mirror of
https://github.com/wekan/wekan.git
synced 2026-02-26 01:44:07 +01:00
merged with wekan master @ v5.38
This commit is contained in:
commit
cb418f5e23
743 changed files with 117634 additions and 43043 deletions
1
packages/markdown/.gitignore
vendored
1
packages/markdown/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
.build*
|
||||
0
packages/markdown/README.md
Executable file → Normal file
0
packages/markdown/README.md
Executable file → Normal file
|
|
@ -1,9 +0,0 @@
|
|||
var mark = marked;
|
||||
|
||||
mark.setOptions({
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: true
|
||||
});
|
||||
|
||||
Markdown = mark;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
# License information
|
||||
|
||||
## Contribution License Agreement
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code
|
||||
to be distributed under the MIT license. You are also implicitly verifying that
|
||||
all code is your original work. `</legalese>`
|
||||
|
||||
## Marked
|
||||
|
||||
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
## Markdown
|
||||
|
||||
Copyright © 2004, John Gruber
|
||||
http://daringfireball.net/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<a href="https://marked.js.org">
|
||||
<img width="60px" height="60px" src="https://marked.js.org/img/logo-black.svg" align="right" />
|
||||
</a>
|
||||
|
||||
# Marked
|
||||
|
||||
[](https://www.npmjs.com/package/marked)
|
||||
[](https://cdn.jsdelivr.net/npm/marked/marked.min.js)
|
||||
[](https://packagephobia.now.sh/result?p=marked)
|
||||
[](https://www.npmjs.com/package/marked)
|
||||
[](https://david-dm.org/markedjs/marked)
|
||||
[](https://david-dm.org/markedjs/marked?type=dev)
|
||||
[](https://travis-ci.org/markedjs/marked)
|
||||
[](https://snyk.io/test/npm/marked)
|
||||
|
||||
- ⚡ built for speed
|
||||
- ⬇️ low-level compiler for parsing markdown without caching or blocking for long periods of time
|
||||
- ⚖️ light-weight while implementing all markdown features from the supported flavors & specifications
|
||||
- 🌐 works in a browser, on a server, or from a command line interface (CLI)
|
||||
|
||||
## Demo
|
||||
|
||||
Checkout the [demo page](https://marked.js.org/demo/) to see marked in action ⛹️
|
||||
|
||||
## Docs
|
||||
|
||||
Our [documentation pages](https://marked.js.org) are also rendered using marked 💯
|
||||
|
||||
Also read about:
|
||||
|
||||
* [Options](https://marked.js.org/#/USING_ADVANCED.md)
|
||||
* [Extensibility](https://marked.js.org/#/USING_PRO.md)
|
||||
|
||||
## Installation
|
||||
|
||||
**CLI:** `npm install -g marked`
|
||||
|
||||
**In-browser:** `npm install marked`
|
||||
|
||||
## Usage
|
||||
|
||||
### Warning: 🚨 Marked does not [sanitize](https://marked.js.org/#/USING_ADVANCED.md#options) the output HTML. Please use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! 🚨
|
||||
|
||||
**CLI**
|
||||
|
||||
``` bash
|
||||
$ marked -o hello.html
|
||||
hello world
|
||||
^D
|
||||
$ cat hello.html
|
||||
<p>hello world</p>
|
||||
```
|
||||
|
||||
**Browser**
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Marked in the browser</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script>
|
||||
document.getElementById('content').innerHTML =
|
||||
marked('# Marked in the browser\n\nRendered by **marked**.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License)
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
The only completely secure system is the one that doesn't exist in the first place.
|
||||
Having said that, we take the security of Marked very seriously.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please disclose potential security issues by email to the project [committers](https://marked.js.org/#/AUTHORS.md) as well as the [listed owners within NPM](https://docs.npmjs.com/cli/owner).
|
||||
We will provide an initial assessment of security reports within 48 hours and should apply patches within 2 weeks
|
||||
(also, feel free to contribute a fix for the issue).
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"extends": "standard",
|
||||
"plugins": [
|
||||
"standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 5,
|
||||
"sourceType": "script"
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"indent": ["error", 2, {
|
||||
"SwitchCase": 1,
|
||||
"VariableDeclarator": { "var": 2 },
|
||||
"outerIIFEBody": 0
|
||||
}],
|
||||
"operator-linebreak": ["error", "before", { "overrides": { "=": "after" } }],
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-cond-assign": "off",
|
||||
"no-useless-escape": "off",
|
||||
"one-var": "off",
|
||||
"no-control-regex": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
|
||||
"prefer-const": "off",
|
||||
"no-var": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"amd": true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
# Authors
|
||||
|
||||
Marked takes an encompassing approach to its community. As such, you can think of these as [concentric circles](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951), where each group encompasses the following groups.
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" style="width:32%">
|
||||
<a href="https://github.com/chjj">
|
||||
<img width="100" height="100" src="https://github.com/chjj.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/chjj">Christopher Jeffrey</a>
|
||||
<div>Original Author</div>
|
||||
<small>Started the fire</small>
|
||||
</td>
|
||||
<td align="center" valign="top" style="width:32%">
|
||||
<a href="https://github.com/joshbruce">
|
||||
<img width="100" height="100" src="https://github.com/joshbruce.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://joshbruce.com">Josh Bruce</a>
|
||||
<div>Publisher</div>
|
||||
<small>Release Wrangler; Humaning Helper; Heckler of Hypertext</small>
|
||||
</td>
|
||||
<td align="center" valign="top" style="width:32%">
|
||||
<a href="https://github.com/styfle">
|
||||
<img width="100" height="100" src="https://github.com/styfle.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://www.ceriously.com">Steven</a>
|
||||
<div>Publisher</div>
|
||||
<small>Release Wrangler; Dr. Docs; Open source, of course; GitHub Guru; Humaning Helper</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/davisjam">
|
||||
<img width="100" height="100" src="https://github.com/davisjam.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/davisjam">Jamie Davis</a>
|
||||
<div>Committer</div>
|
||||
<small>Seeker of Security</small>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/UziTech">
|
||||
<img width="100" height="100" src="https://github.com/UziTech.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://tony.brix.ninja">Tony Brix</a>
|
||||
<div>Publisher</div>
|
||||
<small>Release Wrangler; Titan of the test harness; Dr. DevOps</small>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/intcreator">
|
||||
<img width="100" height="100" src="https://github.com/intcreator.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/intcreator">Brandon der Blätter</a>
|
||||
<div>Contributor</div>
|
||||
<small>Curious Contributor</small>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/carlosvalle">
|
||||
<img width="100" height="100" src="https://github.com/carlosvalle.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/carlosvalle">Carlos Valle</a>
|
||||
<div>Contributor</div>
|
||||
<small>Maker of the Marked mark from 2018 to present</small>
|
||||
</td>
|
||||
<td align="center" width="20%" valign="top">
|
||||
<a href="https://github.com/Feder1co5oave">
|
||||
<img width="100" height="100" src="https://github.com/Feder1co5oave.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/Feder1co5oave">Federico Soave</a>
|
||||
<div>Contributor</div>
|
||||
<small>Regent of the Regex; Master of Marked</small>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/karenyavine">
|
||||
<img width="100" height="100" src="https://github.com/karenyavine.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/karenyavine">Karen Yavine</a>
|
||||
<div>Contributor</div>
|
||||
<small>Snyk's Security Saint</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://github.com/KostyaTretyak">
|
||||
<img width="100" height="100" src="https://github.com/KostyaTretyak.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/KostyaTretyak">Костя Третяк</a>
|
||||
<div>Contributor</div>
|
||||
<small></small>
|
||||
</td>
|
||||
<td align="center" width="20%" valign="top">
|
||||
<a href="https://github.com/tomtheisen">
|
||||
<img width="100" height="100" src="https://github.com/tomtheisen.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/tomtheisen">Tom Theisen</a>
|
||||
<div>Contributor</div>
|
||||
<small>Defibrillator</small>
|
||||
</td>
|
||||
<td align="center" width="20%" valign="top">
|
||||
<a href="https://github.com/mccraveiro">
|
||||
<img width="100" height="100" src="https://github.com/mccraveiro.png?s=150">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://github.com/mccraveiro">Mateus Craveiro</a>
|
||||
<div>Contributor</div>
|
||||
<small>Defibrillator</small>
|
||||
</td>
|
||||
<td align="center" width="20%" valign="top">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Publishers
|
||||
|
||||
Publishers are admins who also have the responsibility, privilege, and burden of publishing the new releases to NPM and performing outreach and external stakeholder communications. Further, when things go pear-shaped, they're the ones taking most of the heat. Finally, when things go well, they're the primary ones praising the contributors who made it possible.
|
||||
|
||||
(In other words, while Admins are focused primarily on the internal workings of the project, Publishers are focused on internal *and* external concerns.)
|
||||
|
||||
**Should not exceed 2:** Having more people with the authority to publish a release can quickly turn into a consensus seeking nightmare (design by committee). Having only one is preferred (Directly Responsible Individual); however, given the nature of the project and its history, having an immediate fallback, and a potential deep fallback (Original author) is probably a good idea.
|
||||
|
||||
[Details on badges](#badges)
|
||||
|
||||
## Admins
|
||||
|
||||
Admins are committers who also have the responsibility, privilege, and burden of selecting committers and making sure the project itself runs smoothly, which includes community maintenance, governance, dispute resolution, and so on. (Letting the contributors easily enter into, and work within, the project to begin contributing, with as little friction as possible.)
|
||||
|
||||
**Should not exceed 3:** When there are too many people with the ability to resolve disputes, the dispute itself can quickly turn into a dispute amongst the admins themselves; therefore, we want this group to be small enough to commit to action and large enough to not put too much burden on one person. (Should ensure faster resolution and responsiveness.)
|
||||
|
||||
To be listed: Admins are usually selected from the pool of committers (or they volunteer, using the same process) who demonstrate good understanding of the marked culture, operations, and do their best to help new contributors get up to speed on how to contribute effectively to the project.
|
||||
|
||||
To be removed: You can remove yourself through the [GitHub UI](https://help.github.com/articles/removing-yourself-from-a-collaborator-s-repository/).
|
||||
|
||||
[Details on badges](#badges)
|
||||
|
||||
## Committers
|
||||
|
||||
Committers are contributors who also have the responsibility, privilege, some might even say burden of being able to review and merge contributions (just usually not their own).
|
||||
|
||||
A note on "decision making authority". This is related to submitting PRs and the [advice process](http://www.reinventingorganizationswiki.com/Decision_Making). The person marked as having decision making authority over a certain area should be sought for advice in that area before committing to a course of action.
|
||||
|
||||
**Should not exceed 5:** For larger PRs affecting more of the codebase and, most likely, review by more people, we try to keep this pool small and responsive and let those with decision making authority have final say without negative repercussions from the other committers.
|
||||
|
||||
To be listed: Committers are usually selected (or they volunteer, using the same process) from contributors who enter the discussions regarding the future direction of Marked (maybe even doing informal reviews of contributions despite not being able to merge them yourself).
|
||||
|
||||
To be removed: You can remove yourself through the [GitHub UI](https://help.github.com/articles/removing-yourself-from-a-collaborator-s-repository/).
|
||||
|
||||
A note on volunteering:
|
||||
|
||||
1. Please do not volunteer unless you believe you can demonstrate to your peers you can do the work required.
|
||||
2. Please do not overcommit yourself; we count on those committed to the project to be responsive. Really consider, with all you have going on, whether you able to really commit to it.
|
||||
3. Don't let the previous frighten you away, it can always be changed later by you or your peers.
|
||||
|
||||
[Details on badges](#badges)
|
||||
|
||||
## Contributors
|
||||
|
||||
Contributors are users who submit a [PR](https://github.com/markedjs/marked/pulls), [Issue](https://github.com/markedjs/marked/issues), or collaborate in making Marked a better product and experience for all the users.
|
||||
|
||||
To be listed: make a contribution and, if it has significant impact, the committers may be able to add you here.
|
||||
|
||||
To be removed: please let us know or submit a PR.
|
||||
|
||||
[Details on badges](#badges)
|
||||
|
||||
## Users
|
||||
|
||||
Users are anyone using Marked in some fashion, without them, there's no reason for us to exist.
|
||||
|
||||
|Individual or Organization |Website |Project |Submitted by |
|
||||
|:--------------------------|:-----------------------|:------------------------------------|:---------------------------------------------------|
|
||||
|MarkedJS |https://marked.js.org |https://github.com/markedjs/marked |The marked committers |
|
||||
|
||||
To be listed: All fields are optional. Contact any of the committers or, more timely, submit a pull request with the following (using the first row as an example):
|
||||
|
||||
- **Individual or Organization:** The name you would like associated with the record.
|
||||
- **Website:** A URL to a standalone website for the project.
|
||||
- **Project:** A URL for the repository of the project using marked.
|
||||
- **Submitted by:** The name and optional honorifics for the person adding the listing.
|
||||
|
||||
To be removed: Same as above. Only instead of requesting addition request deletion or delete the row yourself.
|
||||
|
||||
<h2 id="badges">Badges</h2>
|
||||
|
||||
Badges? You don't *need* no stinkin' badges.
|
||||
|
||||
Movie references aside. (It was either that or, "Let's play a game", but that would have been creepy…that's why it will most likely come later.)
|
||||
|
||||
Badges? If you *want* 'em, we got 'em, and here's how you get 'em (and…dramatic pause…why not two dramatic pauses for emphasis?… how they can be taken away).
|
||||
|
||||
- [ ] Add the appropriate badge to the desired contributor in the desired column of this page, even if they're not listed here yet.
|
||||
- [ ] Submit a PR (we're big on PRs around here, if you haven't noticed, help us help you).
|
||||
- [ ] Follow the instructions for submitting a badge PR. (There are more details to find within. Come on. Everybody likes surprises, right? No? Actually, we just try to put documentation where it belongs, closer to the code and part of the sequence of events.)
|
||||
|
||||
### Badges at play:
|
||||
|
||||
<dl>
|
||||
<dt>Curious Contributor</dt>
|
||||
<dd>A contributor with less than one year on this page who is actively engaged in submitting PRs, Issues, making recommendations, sharing thoughts…without being too annoying about it (let's be clear, submitting 100 Issues recommending the Marked Committers send everyone candy is trying for the badge, not honestly earning it).</dd>
|
||||
<dt>Dr. DevOps</dt>
|
||||
<dd>
|
||||
<p>Someone who understands and contributes to improving the developer experience and flow of Marked into the world.</p>
|
||||
<blockquote>
|
||||
"The main characteristic of the DevOps movement is to strongly advocate automation and monitoring at all steps of software construction, from integration, testing, releasing to deployment and infrastructure management. DevOps aims at shorter development cycles, increased deployment frequency, more dependable releases, in close alignment with business objectives." ~ <a href="https://www.wikipedia.org/wiki/DevOps">Wikipedia</a>
|
||||
</blockquote>
|
||||
</dd>
|
||||
<dt>Dr. Docs</dt>
|
||||
<dd>Someone who has contributed a great deal to the creation and maintenance of the non-code areas of marked.</dd>
|
||||
<dt>Eye for the CLI</dt>
|
||||
<dd>At this point? Pretty much anyone who can update that `man` file to the current Marked version without regression in the CLI tool itself.</dd>
|
||||
<dt>GitHub Guru</dt>
|
||||
<dd>Someone who always seems to be able to tell you easier ways to do things with GitHub.</dd>
|
||||
<dt>Humaning Helper</dt>
|
||||
<dd>Someone who goes out of their way to help contributors feel welcomed and valued. Further, someone who takes the extra steps(s) necessary to help new contributors get up to speed. Finally, they maintain composure even in times of disagreement and dispute resolution.</dd>
|
||||
<dt>Heckler of Hypertext</dt>
|
||||
<dd>Someone who demonstrates an esoteric level of knowledge when it comes to HTML. In other words, someone who says things like, "Did you know most Markdown flavors don't have a way to render a description list (`dl`)? All the more reason Markdown `!==` HTML."</dd>
|
||||
<dt>Markdown Maestro</dt>
|
||||
<dd>You know that person who knows about way too many different flavors of Markdown? The one who maybe seems a little too obsessed with the possibilities of Markdown beyond HTML? Come on. You know who they are. Or, at least you could, if you give them this badge.</dd>
|
||||
<dt>Master of Marked</dt>
|
||||
<dd>Someone who demonstrates they know the ins and outs of the codebase for Marked.</dd>
|
||||
<dt>Open source, of course</dt>
|
||||
<dd>Someone who advocates for and has a proven understanding of how to operate within open source communities.</dd>
|
||||
<dt>Regent of the Regex</dt>
|
||||
<dd><p>Can you demonstrate you understand the following without Google and Stackoverflow?</p>
|
||||
<p><code>/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/</code></p>
|
||||
<p>Because this author can't yet. That's who gets these.</p>
|
||||
</dd>
|
||||
<dt>Seeker of Security</dt>
|
||||
<dd>Someone who has demonstrated a high degree of expertise or authority when it comes to software security.</dd>
|
||||
<dt>Titan of the Test Harness</dt>
|
||||
<dd>Someone who demonstrates high-levels of understanding regarding Marked's test harness.</dd>
|
||||
<dt>Totally Tron</dt>
|
||||
<dd>Someone who demonstrates they are willing and able to "fight for the users", both developers dependent on marked to do their jobs as well as end-users interacting with the output (particularly in the realm of those with the disabilities).</dd>
|
||||
</dl>
|
||||
|
||||
### Special badges that come with the job:
|
||||
|
||||
<dl>
|
||||
<dt>Defibrillator</dt>
|
||||
<dd>A contributor who stepped up to help bring Marked back to life by contributing solutions to help Marked pass when compared against the CommonMark and GitHub Flavored Markdown specifications.</dd>
|
||||
<dt>Maker of the Marked mark</dt>
|
||||
<dd>This badge is given to the person or organization credited with creating the logo (or logotype) used in Marked communications for a given period of time. **Maker of the Marked mark from 2017 to present**, for example.</dd>
|
||||
<dt>Release Wrangler</dt>
|
||||
<dd>This is a badge given to all Publishers.</dd>
|
||||
<dt>Snyk's Security Saint</dt>
|
||||
<dd>This is a badge given to whomever primarily reaches out from Snyk to let us know about security issues.</dd>
|
||||
</dl>
|
||||
|
|
@ -1 +0,0 @@
|
|||
marked.js.org
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to block temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team by submitting a PR with changes to the [AUTHORS](#/AUTHORS.md) page (or emailing josh@8fold.com). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version [1.4][version].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org/
|
||||
[version]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
# Contributing to Marked
|
||||
|
||||
- [ ] Fork `markedjs/marked`.
|
||||
- [ ] Clone the library locally using GitHub Desktop or the command line.
|
||||
- [ ] Make sure you are on the `master` branch.
|
||||
- [ ] Be sure to run `npm install` or `npm update`.
|
||||
- [ ] Create a branch.
|
||||
- [ ] Update code in `src` folder. (`lib` folder is for auto compiled code)
|
||||
- [ ] Run `npm run test:all`, fix any broken things (for linting, you can run `npm run lint` to have the linter fix them for you).
|
||||
- [ ] Run `npm run build:reset` to remove changes to compiled files.
|
||||
- [ ] Submit a Pull Request.
|
||||
|
||||
## Design principles
|
||||
|
||||
Marked tends to favor following the SOLID set of software design and development principles; mainly the [single responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle) and [open/closed principles](https://en.wikipedia.org/wiki/Open/closed_principle):
|
||||
|
||||
- **Single responsibility:** Marked, and the components of Marked, have the single responsibility of converting Markdown strings into HTML.
|
||||
- **Open/closed:** Marked favors giving developers the means to easily extend the library and its components over changing Marked's behavior through configuration options.
|
||||
|
||||
## Priorities
|
||||
|
||||
We think we have our priorities sorted to build quality in.
|
||||
|
||||
The following table lists the ticket type labels we use when there is work to be done on the code either through an Issue or a PR; in priority order.
|
||||
|
||||
|Ticket type label |Description |
|
||||
|:----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|L0 - security |A security vulnerability within the Marked library is discovered. |
|
||||
|L1 - broken |Valid usage results in incorrect output compared to [supported specifications](#/README.md#specifications) OR causes marked to crash AND there is no known workaround for the issue. |
|
||||
|L2 - annoying |Similar to L1 - broken only there is a known workaround available for the issue. |
|
||||
|RR - refactor and re-engineer |Results in an improvement to developers using Marked (improved readability) or end-users (faster performance) or both. |
|
||||
|NFS - new feature (spec related) |A capability Marked does not currently provide but is in one of the [supported specifications](#/README.md#specifications) |
|
||||
|NFU - new feature (user requested) |A capability Marked does not currently provide but has been requested by users of Marked. |
|
||||
|NFE - new feature (should be an extension) |A capability Marked does not currently provide and is not part of a spec. |
|
||||
|
||||
## Test early, often, and everything
|
||||
|
||||
We try to write test cases to validate output (writing tests based on the [supported specifications](#/README.md#specifications)) and minimize regression (writing tests for issues fixed). Therefore, if you would like to contribute, some things you should know regarding the test harness.
|
||||
|
||||
|Location |Description |
|
||||
|:---------------------|:--------------------------------------------------------------------------------------------------------------|
|
||||
|/test/specs/commonmark|Tests for [CommonMark](https://spec.commonmark.org/current/) compliance |
|
||||
|/test/specs/gfm |Tests for [GFM](https://github.github.com/gfm/) compliance |
|
||||
|/test/specs/new |Tests not related to the original `markdown.pl`. |
|
||||
|/test/specs/original |Tests validating against the original `markdown.pl`. |
|
||||
|/test/specs/redos |Tests for [ReDOS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS) vulnerabilities|
|
||||
|
||||
If your test uses features or options, assuming `gfm` is set to `false`, for example, you can add [front-matter](https://www.npmjs.com/package/front-matter) to the top of
|
||||
your `.md` file
|
||||
|
||||
``` yml
|
||||
---
|
||||
gfm: false
|
||||
---
|
||||
```
|
||||
|
||||
## Submitting PRs and Issues
|
||||
|
||||
Marked provides templates for submitting both pull requests and issues. When you begin creating a new PR or issue, you will see instructions on using the template.
|
||||
|
||||
The PR templates include checklists for both the submitter and the reviewer, which, in most cases, will not be the same person.
|
||||
|
||||
## Scripts
|
||||
|
||||
When it comes to NPM commands, we try to use the native scripts provided by the NPM framework.
|
||||
|
||||
To run the tests:
|
||||
|
||||
``` bash
|
||||
npm test
|
||||
```
|
||||
|
||||
To test whether you are using the standard syntax rules for the project:
|
||||
|
||||
```bash
|
||||
npm run test:lint
|
||||
```
|
||||
|
||||
To see time comparisons between Marked and other popular Markdown libraries:
|
||||
|
||||
```bash
|
||||
npm run bench
|
||||
```
|
||||
|
||||
To check for (and fix) standardized syntax (lint):
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
To build your own es5, esm, and minified versions of Marked:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Releasing Marked
|
||||
|
||||
- [ ] See [contributing](#/CONTRIBUTING.md)
|
||||
- [ ] Create release branch from `master` (`release-x.y.z`)
|
||||
- [ ] Submit PR with minimal name: Release x.y.z
|
||||
- [ ] Complete PR checklists
|
||||
|
||||
## Overall strategy
|
||||
|
||||
**Master is always shippable:** We try to merge PRs in such a way that `master` is the only branch to really be concerned about *and* `master` can always be released. This allows smoother flow between new features, bug fixes, and so on. (Almost a continuous deployment setup, without automation.)
|
||||
|
||||
## Versioning
|
||||
|
||||
We follow [semantic versioning](https://semver.org) where the following sequence is true `[major].[minor].[patch]`; therefore, consider the following implications of the release you are preparing:
|
||||
|
||||
1. **Major:** There is at least one change not deemed backward compatible.
|
||||
2. **Minor:** There is at least one new feature added to the release.
|
||||
3. **Patch:** No breaking changes, no new features.
|
||||
|
||||
What to expect while Marked is a zero-major (0.x.y):
|
||||
|
||||
1. The major will remain at zero; thereby, alerting consumers to the potentially volatile nature of the package.
|
||||
2. The minor will tend to be more analogous to a `major` release.
|
||||
3. The patch will tend to be more analogous to a `minor` release or a collection of bug fixes (patches).
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
Marked is
|
||||
|
||||
1. built for speed.<sup>*</sup>
|
||||
2. a low-level markdown compiler for parsing markdown without caching or blocking for long periods of time.<sup>**</sup>
|
||||
3. light-weight while implementing all markdown features from the supported flavors & specifications.<sup>***</sup>
|
||||
4. available as a command line interface (CLI) and running in client- or server-side JavaScript projects.
|
||||
|
||||
<p><small><sup>*</sup> Still working on metrics for comparative analysis and definition.</small><br>
|
||||
<small><sup>**</sup> As few dependencies as possible.</small><br>
|
||||
<small><sup>***</sup> Strict compliance could result in slower processing when running comparative benchmarking.</small></p>
|
||||
|
||||
|
||||
<h2 id="demo">Demo</h2>
|
||||
|
||||
Checkout the [demo page](./demo/) to see marked in action ⛹️
|
||||
|
||||
These documentation pages are also rendered using marked 💯
|
||||
|
||||
|
||||
<h2 id="installation">Installation</h2>
|
||||
|
||||
**CLI:** `npm install -g marked`
|
||||
|
||||
**In-browser:** `npm install marked`
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
### Warning: 🚨 Marked does not [sanitize](https://marked.js.org/#/USING_ADVANCED.md#options) the output HTML. Please use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! 🚨
|
||||
|
||||
**CLI**
|
||||
|
||||
``` bash
|
||||
$ marked -o hello.html
|
||||
hello world
|
||||
^D
|
||||
$ cat hello.html
|
||||
<p>hello world</p>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked -s "*hello world*"
|
||||
<p><em>hello world</em></p>
|
||||
```
|
||||
|
||||
**Browser**
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Marked in the browser</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script>
|
||||
document.getElementById('content').innerHTML =
|
||||
marked('# Marked in browser\n\nRendered by **marked**.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Marked offers [advanced configurations](#/USING_ADVANCED.md) and [extensibility](#/USING_PRO.md) as well.
|
||||
|
||||
<h2 id="specifications">Supported Markdown specifications</h2>
|
||||
|
||||
We actively support the features of the following [Markdown flavors](https://github.com/commonmark/CommonMark/wiki/Markdown-Flavors).
|
||||
|
||||
|Flavor |Version |
|
||||
|:----------------------------------------------------------|:----------|
|
||||
|The original markdown.pl |-- |
|
||||
|[CommonMark](http://spec.commonmark.org/0.29/) |0.29 |
|
||||
|[GitHub Flavored Markdown](https://github.github.com/gfm/) |0.29 |
|
||||
|
||||
By supporting the above Markdown flavors, it's possible that Marked can help you use other flavors as well; however, these are not actively supported by the community.
|
||||
|
||||
<h2 id="security">Security</h2>
|
||||
|
||||
The only completely secure system is the one that doesn't exist in the first place. Having said that, we take the security of Marked very seriously.
|
||||
|
||||
Therefore, please disclose potential security issues by email to the project [committers](#/AUTHORS.md) as well as the [listed owners within NPM](https://docs.npmjs.com/cli/owner). We will provide an initial assessment of security reports within 48 hours and should apply patches within 2 weeks (also, feel free to contribute a fix for the issue).
|
||||
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
## The `marked` function
|
||||
|
||||
```js
|
||||
marked(markdownString [,options] [,callback])
|
||||
```
|
||||
|
||||
|Argument |Type |Notes |
|
||||
|:---------------------|:------------|:----------------------------------------------------------------------------------------------------|
|
||||
|markdownString |`string` |String of markdown source to be compiled. |
|
||||
|<a href="#options">options</a>|`object`|Hash of options. Can also use `marked.setOptions`. |
|
||||
|callback |`function` |Called when `markdownString` has been parsed. Can be used as second argument if no `options` present.|
|
||||
|
||||
### Alternative using reference
|
||||
|
||||
```js
|
||||
// Create reference instance
|
||||
const marked = require('marked');
|
||||
|
||||
// Set options
|
||||
// `highlight` example uses `highlight.js`
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
highlight: function(code, language) {
|
||||
const hljs = require('highlight.js');
|
||||
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext';
|
||||
return hljs.highlight(validLanguage, code).value;
|
||||
},
|
||||
pedantic: false,
|
||||
gfm: true,
|
||||
breaks: false,
|
||||
sanitize: false,
|
||||
smartLists: true,
|
||||
smartypants: false,
|
||||
xhtml: false
|
||||
});
|
||||
|
||||
// Compile
|
||||
console.log(marked(markdownString));
|
||||
```
|
||||
|
||||
<h2 id="options">Options</h2>
|
||||
|
||||
|Member |Type |Default |Since |Notes |
|
||||
|:-----------|:---------|:--------|:--------|:-------------|
|
||||
|baseUrl |`string` |`null` |0.3.9 |A prefix url for any relative link. |
|
||||
|breaks |`boolean` |`false` |v0.2.7 |If true, add `<br>` on a single line break (copies GitHub). Requires `gfm` be `true`.|
|
||||
|gfm |`boolean` |`true` |v0.2.1 |If true, use approved [GitHub Flavored Markdown (GFM) specification](https://github.github.com/gfm/).|
|
||||
|headerIds |`boolean` |`true` |v0.4.0 |If true, include an `id` attribute when emitting headings (h1, h2, h3, etc).|
|
||||
|headerPrefix|`string` |`''` |v0.3.0 |A string to prefix the `id` attribute when emitting headings (h1, h2, h3, etc).|
|
||||
|highlight |`function`|`null` |v0.3.0 |A function to highlight code blocks, see <a href="#highlight">Asynchronous highlighting</a>.|
|
||||
|langPrefix |`string` |`'language-'`|v0.3.0|A string to prefix the className in a `<code>` block. Useful for syntax highlighting.|
|
||||
|mangle |`boolean` |`true` |v0.3.4 |If true, autolinked email address is escaped with HTML character references.|
|
||||
|pedantic |`boolean` |`false` |v0.2.1 |If true, conform to the original `markdown.pl` as much as possible. Don't fix original markdown bugs or behavior. Turns off and overrides `gfm`.|
|
||||
|renderer |`object` |`new Renderer()`|v0.3.0|An object containing functions to render tokens to HTML. See [extensibility](/#/USING_PRO.md) for more details.|
|
||||
|sanitize |`boolean` |`false` |v0.2.1 |If true, sanitize the HTML passed into `markdownString` with the `sanitizer` function.<br>**Warning**: This feature is deprecated and it should NOT be used as it cannot be considered secure.<br>Instead use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! |
|
||||
|sanitizer |`function`|`null` |v0.3.4 |A function to sanitize the HTML passed into `markdownString`.|
|
||||
|silent |`boolean` |`false` |v0.2.7 |If true, the parser does not throw any exception.|
|
||||
|smartLists |`boolean` |`false` |v0.2.8 |If true, use smarter list behavior than those found in `markdown.pl`.|
|
||||
|smartypants |`boolean` |`false` |v0.2.9 |If true, use "smart" typographic punctuation for things like quotes and dashes.|
|
||||
|xhtml |`boolean` |`false` |v0.3.2 |If true, emit self-closing HTML tags for void elements (<br/>, <img/>, etc.) with a "/" as required by XHTML.|
|
||||
|
||||
<h2 id="highlight">Asynchronous highlighting</h2>
|
||||
|
||||
Unlike `highlight.js` the `pygmentize.js` library uses asynchronous highlighting. This example demonstrates that marked is agnostic when it comes to the highlighter you use.
|
||||
|
||||
```js
|
||||
marked.setOptions({
|
||||
highlight: function(code, lang, callback) {
|
||||
require('pygmentize-bundled') ({ lang: lang, format: 'html' }, code, function (err, result) {
|
||||
callback(err, result.toString());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log(marked(markdownString));
|
||||
```
|
||||
|
||||
In both examples, `code` is a `string` representing the section of code to pass to the highlighter. In this example, `lang` is a `string` informing the highlighter what programming language to use for the `code` and `callback` is the `function` the asynchronous highlighter will call once complete.
|
||||
|
||||
<h2 id="workers">Workers</h2>
|
||||
|
||||
To prevent ReDoS attacks you can run marked on a worker and terminate it when parsing takes longer than usual.
|
||||
|
||||
Marked can be run in a [worker thread](https://nodejs.org/api/worker_threads.html) on a node server, or a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) in a browser.
|
||||
|
||||
### Node Worker Thread
|
||||
|
||||
```js
|
||||
// markedWorker.js
|
||||
|
||||
const marked = require('marked');
|
||||
const { parentPort } = require('worker_threads');
|
||||
|
||||
parentPort.on('message', (markdownString) => {
|
||||
parentPort.postMessage(marked(markdownString));
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// index.js
|
||||
|
||||
const { Worker } = require('worker_threads');
|
||||
const markedWorker = new Worker('./markedWorker.js');
|
||||
|
||||
const markedTimeout = setTimeout(() => {
|
||||
markedWorker.terminate();
|
||||
throw new Error('Marked took too long!');
|
||||
}, timeoutLimit);
|
||||
|
||||
markedWorker.on('message', (html) => {
|
||||
clearTimeout(markedTimeout);
|
||||
console.log(html);
|
||||
markedWorker.terminate();
|
||||
});
|
||||
|
||||
markedWorker.postMessage(markdownString);
|
||||
```
|
||||
|
||||
### Web Worker
|
||||
|
||||
> **NOTE**: Web Workers send the payload from `postMessage` in an object with the payload in a `.data` property
|
||||
|
||||
```js
|
||||
// markedWorker.js
|
||||
|
||||
importScripts('path/to/marked.min.js');
|
||||
|
||||
onmessage = (e) => {
|
||||
const markdownString = e.data
|
||||
postMessage(marked(markdownString));
|
||||
};
|
||||
```
|
||||
|
||||
```js
|
||||
// script.js
|
||||
|
||||
const markedWorker = new Worker('./markedWorker.js');
|
||||
|
||||
const markedTimeout = setTimeout(() => {
|
||||
markedWorker.terminate();
|
||||
throw new Error('Marked took too long!');
|
||||
}, timeoutLimit);
|
||||
|
||||
markedWorker.onmessage = (e) => {
|
||||
clearTimeout(markedTimeout);
|
||||
const html = e.data;
|
||||
console.log(html);
|
||||
markedWorker.terminate();
|
||||
};
|
||||
|
||||
markedWorker.postMessage(markdownString);
|
||||
```
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
## Extending Marked
|
||||
|
||||
To champion the single-responsibility and open/closed principles, we have tried to make it relatively painless to extend marked. If you are looking to add custom functionality, this is the place to start.
|
||||
|
||||
<h2 id="renderer">The renderer</h2>
|
||||
|
||||
The renderer is...
|
||||
|
||||
**Example:** Overriding default heading token by adding an embedded anchor tag like on GitHub.
|
||||
|
||||
```js
|
||||
// Create reference instance
|
||||
const marked = require('marked');
|
||||
|
||||
// Get reference
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
// Override function
|
||||
renderer.heading = function (text, level) {
|
||||
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
|
||||
|
||||
return `
|
||||
<h${level}>
|
||||
<a name="${escapedText}" class="anchor" href="#${escapedText}">
|
||||
<span class="header-link"></span>
|
||||
</a>
|
||||
${text}
|
||||
</h${level}>`;
|
||||
};
|
||||
|
||||
// Run marked
|
||||
console.log(marked('# heading+', { renderer: renderer }));
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```html
|
||||
<h1>
|
||||
<a name="heading-" class="anchor" href="#heading-">
|
||||
<span class="header-link"></span>
|
||||
</a>
|
||||
heading+
|
||||
</h1>
|
||||
```
|
||||
|
||||
### Block level renderer methods
|
||||
|
||||
- code(*string* code, *string* infostring, *boolean* escaped)
|
||||
- blockquote(*string* quote)
|
||||
- html(*string* html)
|
||||
- heading(*string* text, *number* level, *string* raw, *Slugger* slugger)
|
||||
- hr()
|
||||
- list(*string* body, *boolean* ordered, *number* start)
|
||||
- listitem(*string* text, *boolean* task, *boolean* checked)
|
||||
- checkbox(*boolean* checked)
|
||||
- paragraph(*string* text)
|
||||
- table(*string* header, *string* body)
|
||||
- tablerow(*string* content)
|
||||
- tablecell(*string* content, *object* flags)
|
||||
|
||||
`slugger` has the `slug` method to create an unique id from value:
|
||||
|
||||
```js
|
||||
slugger.slug('foo') // foo
|
||||
slugger.slug('foo') // foo-1
|
||||
slugger.slug('foo') // foo-2
|
||||
slugger.slug('foo 1') // foo-1-1
|
||||
slugger.slug('foo-1') // foo-1-2
|
||||
...
|
||||
```
|
||||
|
||||
`flags` has the following properties:
|
||||
|
||||
```js
|
||||
{
|
||||
header: true || false,
|
||||
align: 'center' || 'left' || 'right'
|
||||
}
|
||||
```
|
||||
|
||||
### Inline level renderer methods
|
||||
|
||||
- strong(*string* text)
|
||||
- em(*string* text)
|
||||
- codespan(*string* code)
|
||||
- br()
|
||||
- del(*string* text)
|
||||
- link(*string* href, *string* title, *string* text)
|
||||
- image(*string* href, *string* title, *string* text)
|
||||
- text(*string* text)
|
||||
|
||||
<h2 id="lexer">The lexer</h2>
|
||||
|
||||
The lexer is...
|
||||
|
||||
|
||||
<h2 id="parser">The parser</h2>
|
||||
|
||||
The parser is...
|
||||
|
||||
***
|
||||
|
||||
<h2 id="extend">Access to lexer and parser</h2>
|
||||
|
||||
You also have direct access to the lexer and parser if you so desire.
|
||||
|
||||
``` js
|
||||
const tokens = marked.lexer(text, options);
|
||||
console.log(marked.parser(tokens, options));
|
||||
```
|
||||
|
||||
``` js
|
||||
const lexer = new marked.Lexer(options);
|
||||
const tokens = lexer.lex(text);
|
||||
console.log(tokens);
|
||||
console.log(lexer.rules);
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ node
|
||||
> require('marked').lexer('> i am using marked.')
|
||||
[ { type: 'blockquote_start' },
|
||||
{ type: 'paragraph',
|
||||
text: 'i am using marked.' },
|
||||
{ type: 'blockquote_end' },
|
||||
links: {} ]
|
||||
```
|
||||
|
||||
The Lexers build an array of tokens, which will be passed to their respective
|
||||
Parsers. The Parsers process each token in the token arrays,
|
||||
which are removed from the array of tokens:
|
||||
|
||||
``` js
|
||||
const marked = require('marked');
|
||||
|
||||
const md = `
|
||||
# heading
|
||||
|
||||
[link][1]
|
||||
|
||||
[1]: #heading "heading"
|
||||
`;
|
||||
|
||||
const tokens = marked.lexer(md);
|
||||
console.log(tokens);
|
||||
|
||||
const html = marked.parser(tokens);
|
||||
console.log(html);
|
||||
|
||||
console.log(tokens);
|
||||
```
|
||||
|
||||
``` bash
|
||||
[ { type: 'heading', depth: 1, text: 'heading' },
|
||||
{ type: 'paragraph', text: ' [link][1]' },
|
||||
{ type: 'space' },
|
||||
links: { '1': { href: '#heading', title: 'heading' } } ]
|
||||
|
||||
<h1 id="heading">heading</h1>
|
||||
<p> <a href="#heading" title="heading">link</a></p>
|
||||
|
||||
[ links: { '1': { href: '#heading', title: 'heading' } } ]
|
||||
```
|
||||
|
|
@ -1,426 +0,0 @@
|
|||
# Markdown is broken
|
||||
|
||||
I have a lot of scraps of markdown engine oddities that I've collected over the
|
||||
years. What you see below is slightly messy, but it's what I've managed to
|
||||
cobble together to illustrate the differences between markdown engines, and
|
||||
why, if there ever is a markdown specification, it has to be absolutely
|
||||
thorough. There are a lot more of these little differences I have documented
|
||||
elsewhere. I know I will find them lingering on my disk one day, but until
|
||||
then, I'll continue to add whatever strange nonsensical things I find.
|
||||
|
||||
Some of these examples may only mention a particular engine compared to marked.
|
||||
However, the examples with markdown.pl could easily be swapped out for
|
||||
discount, upskirt, or markdown.js, and you would very easily see even more
|
||||
inconsistencies.
|
||||
|
||||
A lot of this was written when I was very unsatisfied with the inconsistencies
|
||||
between markdown engines. Please excuse the frustration noticeable in my
|
||||
writing.
|
||||
|
||||
## Examples of markdown's "stupid" list parsing
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
|
||||
* item1
|
||||
|
||||
* item2
|
||||
|
||||
text
|
||||
^D
|
||||
<ul>
|
||||
<li><p>item1</p>
|
||||
|
||||
<ul>
|
||||
<li>item2</li>
|
||||
</ul>
|
||||
|
||||
<p><p>text</p></li>
|
||||
</ul></p>
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
$ marked
|
||||
* item1
|
||||
|
||||
* item2
|
||||
|
||||
text
|
||||
^D
|
||||
<ul>
|
||||
<li><p>item1</p>
|
||||
<ul>
|
||||
<li>item2</li>
|
||||
</ul>
|
||||
<p>text</p>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Which looks correct to you?
|
||||
|
||||
- - -
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<p><ul>
|
||||
<li>hello</p>
|
||||
|
||||
<blockquote>
|
||||
<p>world</li>
|
||||
</ul></p>
|
||||
</blockquote>
|
||||
```
|
||||
|
||||
```
|
||||
$ marked
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<ul>
|
||||
<li>hello<blockquote>
|
||||
<p>world</p>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Again, which looks correct to you?
|
||||
|
||||
- - -
|
||||
|
||||
EXAMPLE:
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
* hello
|
||||
* world
|
||||
* hi
|
||||
code
|
||||
^D
|
||||
<ul>
|
||||
<li>hello
|
||||
<ul>
|
||||
<li>world</li>
|
||||
<li>hi
|
||||
code</li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
The code isn't a code block even though it's after the bullet margin. I know,
|
||||
lets give it two more spaces, effectively making it 8 spaces past the bullet.
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
* hello
|
||||
* world
|
||||
* hi
|
||||
code
|
||||
^D
|
||||
<ul>
|
||||
<li>hello
|
||||
<ul>
|
||||
<li>world</li>
|
||||
<li>hi
|
||||
code</li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
And, it's still not a code block. Did you also notice that the 3rd item isn't
|
||||
even its own list? Markdown screws that up too because of its indentation
|
||||
unaware parsing.
|
||||
|
||||
- - -
|
||||
|
||||
Let's look at some more examples of markdown's list parsing:
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
|
||||
* item1
|
||||
|
||||
* item2
|
||||
|
||||
text
|
||||
^D
|
||||
<ul>
|
||||
<li><p>item1</p>
|
||||
|
||||
<ul>
|
||||
<li>item2</li>
|
||||
</ul>
|
||||
|
||||
<p><p>text</p></li>
|
||||
</ul></p>
|
||||
```
|
||||
|
||||
Misnested tags.
|
||||
|
||||
|
||||
```
|
||||
$ marked
|
||||
* item1
|
||||
|
||||
* item2
|
||||
|
||||
text
|
||||
^D
|
||||
<ul>
|
||||
<li><p>item1</p>
|
||||
<ul>
|
||||
<li>item2</li>
|
||||
</ul>
|
||||
<p>text</p>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Which looks correct to you?
|
||||
|
||||
- - -
|
||||
|
||||
```
|
||||
$ markdown.pl
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<p><ul>
|
||||
<li>hello</p>
|
||||
|
||||
<blockquote>
|
||||
<p>world</li>
|
||||
</ul></p>
|
||||
</blockquote>
|
||||
```
|
||||
|
||||
More misnested tags.
|
||||
|
||||
|
||||
```
|
||||
$ marked
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<ul>
|
||||
<li>hello<blockquote>
|
||||
<p>world</p>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Again, which looks correct to you?
|
||||
|
||||
- - -
|
||||
|
||||
# Why quality matters - Part 2
|
||||
|
||||
``` bash
|
||||
$ markdown.pl
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<p><ul>
|
||||
<li>hello</p>
|
||||
|
||||
<blockquote>
|
||||
<p>world</li>
|
||||
</ul></p>
|
||||
</blockquote>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ sundown # upskirt
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<ul>
|
||||
<li>hello
|
||||
> world</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked
|
||||
* hello
|
||||
> world
|
||||
^D
|
||||
<ul><li>hello <blockquote><p>world</p></blockquote></li></ul>
|
||||
```
|
||||
|
||||
Which looks correct to you?
|
||||
|
||||
- - -
|
||||
|
||||
See: https://github.com/evilstreak/markdown-js/issues/23
|
||||
|
||||
``` bash
|
||||
$ markdown.pl # upskirt/markdown.js/discount
|
||||
* hello
|
||||
var a = 1;
|
||||
* world
|
||||
^D
|
||||
<ul>
|
||||
<li>hello
|
||||
var a = 1;</li>
|
||||
<li>world</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked
|
||||
* hello
|
||||
var a = 1;
|
||||
* world
|
||||
^D
|
||||
<ul><li>hello
|
||||
<pre>code>var a = 1;</code></pre></li>
|
||||
<li>world</li></ul>
|
||||
```
|
||||
|
||||
Which looks more reasonable? Why shouldn't code blocks be able to appear in
|
||||
list items in a sane way?
|
||||
|
||||
- - -
|
||||
|
||||
``` bash
|
||||
$ markdown.js
|
||||
<div>hello</div>
|
||||
|
||||
<span>hello</span>
|
||||
^D
|
||||
<p><div>hello</div></p>
|
||||
|
||||
<p><span>hello</span></p>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked
|
||||
<div>hello</div>
|
||||
|
||||
<span>hello</span>
|
||||
^D
|
||||
<div>hello</div>
|
||||
|
||||
|
||||
<p><span>hello</span>
|
||||
</p>
|
||||
```
|
||||
|
||||
- - -
|
||||
|
||||
See: https://github.com/evilstreak/markdown-js/issues/27
|
||||
|
||||
``` bash
|
||||
$ markdown.js
|
||||
[](/link)
|
||||
^D
|
||||
<p><a href="/image)](/link">](/link)
|
||||
^D
|
||||
<p><a href="/link"><img src="/image" alt="an image"></a>
|
||||
</p>
|
||||
```
|
||||
|
||||
- - -
|
||||
|
||||
See: https://github.com/evilstreak/markdown-js/issues/24
|
||||
|
||||
``` bash
|
||||
$ markdown.js
|
||||
> a
|
||||
|
||||
> b
|
||||
|
||||
> c
|
||||
^D
|
||||
<blockquote><p>a</p><p>bundefined> c</p></blockquote>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked
|
||||
> a
|
||||
|
||||
> b
|
||||
|
||||
> c
|
||||
^D
|
||||
<blockquote><p>a
|
||||
|
||||
</p></blockquote>
|
||||
<blockquote><p>b
|
||||
|
||||
</p></blockquote>
|
||||
<blockquote><p>c
|
||||
</p></blockquote>
|
||||
```
|
||||
|
||||
- - -
|
||||
|
||||
``` bash
|
||||
$ markdown.pl
|
||||
* hello
|
||||
* world
|
||||
how
|
||||
|
||||
are
|
||||
you
|
||||
|
||||
* today
|
||||
* hi
|
||||
^D
|
||||
<ul>
|
||||
<li><p>hello</p>
|
||||
|
||||
<ul>
|
||||
<li>world
|
||||
how</li>
|
||||
</ul>
|
||||
|
||||
<p>are
|
||||
you</p>
|
||||
|
||||
<ul>
|
||||
<li>today</li>
|
||||
</ul></li>
|
||||
<li>hi</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
``` bash
|
||||
$ marked
|
||||
* hello
|
||||
* world
|
||||
how
|
||||
|
||||
are
|
||||
you
|
||||
|
||||
* today
|
||||
* hi
|
||||
^D
|
||||
<ul>
|
||||
<li><p>hello</p>
|
||||
<ul>
|
||||
<li><p>world
|
||||
how</p>
|
||||
<p>are
|
||||
you</p>
|
||||
</li>
|
||||
<li><p>today</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>hi</li>
|
||||
</ul>
|
||||
```
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #333;
|
||||
background-color: #fbfbfb;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.github-ribbon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.containers {
|
||||
display: flex;
|
||||
height: calc(100vh - 68px);
|
||||
}
|
||||
|
||||
.container {
|
||||
flex-basis: 50%;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pane, .inputPane {
|
||||
margin-top: 5px;
|
||||
padding: 0.6em;
|
||||
border: 1px solid #ccc;
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
#preview {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#preview iframe {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
border-color: red;
|
||||
background-color: #FEE
|
||||
}
|
||||
|
|
@ -1,534 +0,0 @@
|
|||
/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare
|
||||
|
||||
if (!window.Promise) {
|
||||
window.Promise = ES6Promise;
|
||||
}
|
||||
if (!window.fetch) {
|
||||
window.fetch = unfetch;
|
||||
}
|
||||
|
||||
onunhandledrejection = function(e) {
|
||||
throw e.reason;
|
||||
};
|
||||
|
||||
var $loadingElem = document.querySelector('#loading');
|
||||
var $mainElem = document.querySelector('#main');
|
||||
var $markdownElem = document.querySelector('#markdown');
|
||||
var $markedVerElem = document.querySelector('#markedVersion');
|
||||
var $commitVerElem = document.querySelector('#commitVersion');
|
||||
var $markedVer = document.querySelector('#markedCdn');
|
||||
var $optionsElem = document.querySelector('#options');
|
||||
var $outputTypeElem = document.querySelector('#outputType');
|
||||
var $inputTypeElem = document.querySelector('#inputType');
|
||||
var $responseTimeElem = document.querySelector('#responseTime');
|
||||
var $previewElem = document.querySelector('#preview');
|
||||
var $previewIframe = document.querySelector('#preview iframe');
|
||||
var $permalinkElem = document.querySelector('#permalink');
|
||||
var $clearElem = document.querySelector('#clear');
|
||||
var $htmlElem = document.querySelector('#html');
|
||||
var $lexerElem = document.querySelector('#lexer');
|
||||
var $panes = document.querySelectorAll('.pane');
|
||||
var $inputPanes = document.querySelectorAll('.inputPane');
|
||||
var lastInput = '';
|
||||
var inputDirty = true;
|
||||
var $activeOutputElem = null;
|
||||
var search = searchToObject();
|
||||
var markedVersions = {
|
||||
master: 'https://cdn.jsdelivr.net/gh/markedjs/marked/lib/marked.js'
|
||||
};
|
||||
var markedVersionCache = {};
|
||||
var delayTime = 1;
|
||||
var checkChangeTimeout = null;
|
||||
var markedWorker;
|
||||
|
||||
$previewIframe.addEventListener('load', handleIframeLoad);
|
||||
|
||||
$outputTypeElem.addEventListener('change', handleOutputChange, false);
|
||||
|
||||
$inputTypeElem.addEventListener('change', handleInputChange, false);
|
||||
|
||||
$markedVerElem.addEventListener('change', handleVersionChange, false);
|
||||
|
||||
$markdownElem.addEventListener('change', handleInput, false);
|
||||
$markdownElem.addEventListener('keyup', handleInput, false);
|
||||
$markdownElem.addEventListener('keypress', handleInput, false);
|
||||
$markdownElem.addEventListener('keydown', handleInput, false);
|
||||
|
||||
$optionsElem.addEventListener('change', handleInput, false);
|
||||
$optionsElem.addEventListener('keyup', handleInput, false);
|
||||
$optionsElem.addEventListener('keypress', handleInput, false);
|
||||
$optionsElem.addEventListener('keydown', handleInput, false);
|
||||
|
||||
$commitVerElem.style.display = 'none';
|
||||
$commitVerElem.addEventListener('keypress', handleAddVersion, false);
|
||||
|
||||
$clearElem.addEventListener('click', handleClearClick, false);
|
||||
|
||||
Promise.all([
|
||||
setInitialQuickref(),
|
||||
setInitialOutputType(),
|
||||
setInitialText(),
|
||||
setInitialVersion()
|
||||
.then(setInitialOptions)
|
||||
]).then(function() {
|
||||
handleInputChange();
|
||||
handleOutputChange();
|
||||
checkForChanges();
|
||||
setScrollPercent(0);
|
||||
$loadingElem.style.display = 'none';
|
||||
$mainElem.style.display = 'block';
|
||||
});
|
||||
|
||||
function setInitialText() {
|
||||
if ('text' in search) {
|
||||
$markdownElem.value = search.text;
|
||||
} else {
|
||||
return fetch('./initial.md')
|
||||
.then(function(res) { return res.text(); })
|
||||
.then(function(text) {
|
||||
if ($markdownElem.value === '') {
|
||||
$markdownElem.value = text;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setInitialQuickref() {
|
||||
return fetch('./quickref.md')
|
||||
.then(function(res) { return res.text(); })
|
||||
.then(function(text) {
|
||||
document.querySelector('#quickref').value = text;
|
||||
});
|
||||
}
|
||||
|
||||
function setInitialVersion() {
|
||||
return fetch('https://data.jsdelivr.com/v1/package/npm/marked')
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
})
|
||||
.then(function(json) {
|
||||
for (var i = 0; i < json.versions.length; i++) {
|
||||
var ver = json.versions[i];
|
||||
markedVersions[ver] = 'https://cdn.jsdelivr.net/npm/marked@' + ver + '/lib/marked.js';
|
||||
var opt = document.createElement('option');
|
||||
opt.textContent = ver;
|
||||
opt.value = ver;
|
||||
$markedVerElem.appendChild(opt);
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
return fetch('https://api.github.com/repos/markedjs/marked/commits')
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
})
|
||||
.then(function(json) {
|
||||
markedVersions.master = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + json[0].sha + '/lib/marked.js';
|
||||
})
|
||||
.catch(function() {
|
||||
// do nothing
|
||||
// uses url without commit
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
if (search.version) {
|
||||
if (markedVersions[search.version]) {
|
||||
return search.version;
|
||||
} else {
|
||||
var match = search.version.match(/^(\w+):(.+)$/);
|
||||
if (match) {
|
||||
switch (match[1]) {
|
||||
case 'commit':
|
||||
addCommitVersion(search.version, match[2].substring(0, 7), match[2]);
|
||||
return search.version;
|
||||
case 'pr':
|
||||
return getPrCommit(match[2])
|
||||
.then(function(commit) {
|
||||
if (!commit) {
|
||||
return 'master';
|
||||
}
|
||||
addCommitVersion(search.version, 'PR #' + match[2], commit);
|
||||
return search.version;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'master';
|
||||
})
|
||||
.then(function(version) {
|
||||
$markedVerElem.value = version;
|
||||
})
|
||||
.then(updateVersion);
|
||||
}
|
||||
|
||||
function setInitialOptions() {
|
||||
if ('options' in search) {
|
||||
$optionsElem.value = search.options;
|
||||
} else {
|
||||
setDefaultOptions();
|
||||
}
|
||||
}
|
||||
|
||||
function setInitialOutputType() {
|
||||
if (search.outputType) {
|
||||
$outputTypeElem.value = search.outputType;
|
||||
}
|
||||
}
|
||||
|
||||
function handleIframeLoad() {
|
||||
lastInput = '';
|
||||
inputDirty = true;
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
inputDirty = true;
|
||||
};
|
||||
|
||||
function handleVersionChange() {
|
||||
if ($markedVerElem.value === 'commit' || $markedVerElem.value === 'pr') {
|
||||
$commitVerElem.style.display = '';
|
||||
} else {
|
||||
$commitVerElem.style.display = 'none';
|
||||
updateVersion();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClearClick() {
|
||||
$markdownElem.value = '';
|
||||
$markedVerElem.value = 'master';
|
||||
$commitVerElem.style.display = 'none';
|
||||
updateVersion().then(setDefaultOptions);
|
||||
}
|
||||
|
||||
function handleAddVersion(e) {
|
||||
if (e.which === 13) {
|
||||
switch ($markedVerElem.value) {
|
||||
case 'commit':
|
||||
var commit = $commitVerElem.value.toLowerCase();
|
||||
if (!commit.match(/^[0-9a-f]{40}$/)) {
|
||||
alert('That is not a valid commit');
|
||||
return;
|
||||
}
|
||||
addCommitVersion('commit:' + commit, commit.substring(0, 7), commit);
|
||||
$markedVerElem.value = 'commit:' + commit;
|
||||
$commitVerElem.style.display = 'none';
|
||||
$commitVerElem.value = '';
|
||||
updateVersion();
|
||||
break;
|
||||
case 'pr':
|
||||
$commitVerElem.disabled = true;
|
||||
var pr = $commitVerElem.value.replace(/\D/g, '');
|
||||
getPrCommit(pr)
|
||||
.then(function(commit) {
|
||||
$commitVerElem.disabled = false;
|
||||
if (!commit) {
|
||||
alert('That is not a valid PR');
|
||||
return;
|
||||
}
|
||||
addCommitVersion('pr:' + pr, 'PR #' + pr, commit);
|
||||
$markedVerElem.value = 'pr:' + pr;
|
||||
$commitVerElem.style.display = 'none';
|
||||
$commitVerElem.value = '';
|
||||
updateVersion();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputChange() {
|
||||
handleChange($inputPanes, $inputTypeElem.value);
|
||||
}
|
||||
|
||||
function handleOutputChange() {
|
||||
$activeOutputElem = handleChange($panes, $outputTypeElem.value);
|
||||
updateLink();
|
||||
}
|
||||
|
||||
function handleChange(panes, visiblePane) {
|
||||
var active = null;
|
||||
for (var i = 0; i < panes.length; i++) {
|
||||
if (panes[i].id === visiblePane) {
|
||||
panes[i].style.display = '';
|
||||
active = panes[i];
|
||||
} else {
|
||||
panes[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
return active;
|
||||
};
|
||||
|
||||
function addCommitVersion(value, text, commit) {
|
||||
if (markedVersions[value]) {
|
||||
return;
|
||||
}
|
||||
markedVersions[value] = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + commit + '/lib/marked.js';
|
||||
var opt = document.createElement('option');
|
||||
opt.textContent = text;
|
||||
opt.value = value;
|
||||
$markedVerElem.insertBefore(opt, $markedVerElem.firstChild);
|
||||
}
|
||||
|
||||
function getPrCommit(pr) {
|
||||
return fetch('https://api.github.com/repos/markedjs/marked/pulls/' + pr + '/commits')
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
})
|
||||
.then(function(json) {
|
||||
return json[json.length - 1].sha;
|
||||
}).catch(function() {
|
||||
// return undefined
|
||||
});
|
||||
}
|
||||
|
||||
function setDefaultOptions() {
|
||||
if (window.Worker) {
|
||||
messageWorker({
|
||||
task: 'defaults',
|
||||
version: markedVersions[$markedVerElem.value]
|
||||
});
|
||||
} else {
|
||||
var defaults = marked.getDefaults();
|
||||
setOptions(defaults);
|
||||
}
|
||||
}
|
||||
|
||||
function setOptions(opts) {
|
||||
$optionsElem.value = JSON.stringify(
|
||||
opts,
|
||||
function(key, value) {
|
||||
if (value && typeof value === 'object' && Object.getPrototypeOf(value) !== Object.prototype) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}, ' ');
|
||||
}
|
||||
|
||||
function searchToObject() {
|
||||
// modified from https://stackoverflow.com/a/7090123/806777
|
||||
var pairs = location.search.slice(1).split('&');
|
||||
var obj = {};
|
||||
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
if (pairs[i] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var pair = pairs[i].split('=');
|
||||
|
||||
obj[decodeURIComponent(pair.shift())] = decodeURIComponent(pair.join('='));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function jsonString(input) {
|
||||
var output = (input + '')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\f/g, '\\f')
|
||||
.replace(/[\\"']/g, '\\$&')
|
||||
.replace(/\u0000/g, '\\0');
|
||||
return '"' + output + '"';
|
||||
};
|
||||
|
||||
function getScrollSize() {
|
||||
var e = $activeOutputElem;
|
||||
|
||||
return e.scrollHeight - e.clientHeight;
|
||||
};
|
||||
|
||||
function getScrollPercent() {
|
||||
var size = getScrollSize();
|
||||
|
||||
if (size <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $activeOutputElem.scrollTop / size;
|
||||
};
|
||||
|
||||
function setScrollPercent(percent) {
|
||||
$activeOutputElem.scrollTop = percent * getScrollSize();
|
||||
};
|
||||
|
||||
function updateLink() {
|
||||
var outputType = '';
|
||||
if ($outputTypeElem.value !== 'preview') {
|
||||
outputType = 'outputType=' + $outputTypeElem.value + '&';
|
||||
}
|
||||
|
||||
$permalinkElem.href = '?' + outputType + 'text=' + encodeURIComponent($markdownElem.value)
|
||||
+ '&options=' + encodeURIComponent($optionsElem.value)
|
||||
+ '&version=' + encodeURIComponent($markedVerElem.value);
|
||||
history.replaceState('', document.title, $permalinkElem.href);
|
||||
}
|
||||
|
||||
function updateVersion() {
|
||||
if (window.Worker) {
|
||||
handleInput();
|
||||
return Promise.resolve();
|
||||
}
|
||||
var promise;
|
||||
if (markedVersionCache[$markedVerElem.value]) {
|
||||
promise = Promise.resolve(markedVersionCache[$markedVerElem.value]);
|
||||
} else {
|
||||
promise = fetch(markedVersions[$markedVerElem.value])
|
||||
.then(function(res) { return res.text(); })
|
||||
.then(function(text) {
|
||||
markedVersionCache[$markedVerElem.value] = text;
|
||||
return text;
|
||||
});
|
||||
}
|
||||
return promise.then(function(text) {
|
||||
var script = document.createElement('script');
|
||||
script.textContent = text;
|
||||
|
||||
$markedVer.parentNode.replaceChild(script, $markedVer);
|
||||
$markedVer = script;
|
||||
}).then(handleInput);
|
||||
}
|
||||
|
||||
function checkForChanges() {
|
||||
if (inputDirty && $markedVerElem.value !== 'commit' && $markedVerElem.value !== 'pr' && (typeof marked !== 'undefined' || window.Worker)) {
|
||||
inputDirty = false;
|
||||
|
||||
updateLink();
|
||||
|
||||
var options = {};
|
||||
var optionsString = $optionsElem.value || '{}';
|
||||
try {
|
||||
var newOptions = JSON.parse(optionsString);
|
||||
options = newOptions;
|
||||
$optionsElem.classList.remove('error');
|
||||
} catch (err) {
|
||||
$optionsElem.classList.add('error');
|
||||
}
|
||||
|
||||
var version = markedVersions[$markedVerElem.value];
|
||||
var markdown = $markdownElem.value;
|
||||
var hash = version + markdown + optionsString;
|
||||
if (lastInput !== hash) {
|
||||
lastInput = hash;
|
||||
if (window.Worker) {
|
||||
delayTime = 100;
|
||||
messageWorker({
|
||||
task: 'parse',
|
||||
version: version,
|
||||
markdown: markdown,
|
||||
options: options
|
||||
});
|
||||
} else {
|
||||
var startTime = new Date();
|
||||
var lexed = marked.lexer(markdown, options);
|
||||
var lexedList = [];
|
||||
for (var i = 0; i < lexed.length; i++) {
|
||||
var lexedLine = [];
|
||||
for (var j in lexed[i]) {
|
||||
lexedLine.push(j + ':' + jsonString(lexed[i][j]));
|
||||
}
|
||||
lexedList.push('{' + lexedLine.join(', ') + '}');
|
||||
}
|
||||
var parsed = marked.parser(lexed, options);
|
||||
var scrollPercent = getScrollPercent();
|
||||
setParsed(parsed, lexedList.join('\n'));
|
||||
setScrollPercent(scrollPercent);
|
||||
var endTime = new Date();
|
||||
delayTime = endTime - startTime;
|
||||
setResponseTime(delayTime);
|
||||
if (delayTime < 50) {
|
||||
delayTime = 50;
|
||||
} else if (delayTime > 500) {
|
||||
delayTime = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkChangeTimeout = window.setTimeout(checkForChanges, delayTime);
|
||||
};
|
||||
|
||||
function setResponseTime(ms) {
|
||||
var amount = ms;
|
||||
var suffix = 'ms';
|
||||
if (ms > 1000 * 60 * 60) {
|
||||
amount = 'Too Long';
|
||||
suffix = '';
|
||||
} else if (ms > 1000 * 60) {
|
||||
amount = '>' + Math.floor(ms / (1000 * 60));
|
||||
suffix = 'm';
|
||||
} else if (ms > 1000) {
|
||||
amount = '>' + Math.floor(ms / 1000);
|
||||
suffix = 's';
|
||||
}
|
||||
$responseTimeElem.textContent = amount + suffix;
|
||||
}
|
||||
|
||||
function setParsed(parsed, lexed) {
|
||||
try {
|
||||
$previewIframe.contentDocument.body.innerHTML = parsed;
|
||||
} catch (ex) {}
|
||||
$htmlElem.value = parsed;
|
||||
$lexerElem.value = lexed;
|
||||
}
|
||||
|
||||
function messageWorker(message) {
|
||||
if (!markedWorker || markedWorker.working) {
|
||||
if (markedWorker) {
|
||||
clearTimeout(markedWorker.timeout);
|
||||
markedWorker.terminate();
|
||||
}
|
||||
markedWorker = new Worker('worker.js');
|
||||
markedWorker.onmessage = function(e) {
|
||||
clearTimeout(markedWorker.timeout);
|
||||
markedWorker.working = false;
|
||||
switch (e.data.task) {
|
||||
case 'defaults':
|
||||
setOptions(e.data.defaults);
|
||||
break;
|
||||
case 'parse':
|
||||
$previewElem.classList.remove('error');
|
||||
$htmlElem.classList.remove('error');
|
||||
$lexerElem.classList.remove('error');
|
||||
var scrollPercent = getScrollPercent();
|
||||
setParsed(e.data.parsed, e.data.lexed);
|
||||
setScrollPercent(scrollPercent);
|
||||
setResponseTime(e.data.time);
|
||||
break;
|
||||
}
|
||||
clearTimeout(checkChangeTimeout);
|
||||
delayTime = 10;
|
||||
checkForChanges();
|
||||
};
|
||||
markedWorker.onerror = markedWorker.onmessageerror = function(err) {
|
||||
clearTimeout(markedWorker.timeout);
|
||||
var error = 'There was an error in the Worker';
|
||||
if (err) {
|
||||
if (err.message) {
|
||||
error = err.message;
|
||||
} else {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
error = error.replace(/^Uncaught Error: /, '');
|
||||
$previewElem.classList.add('error');
|
||||
$htmlElem.classList.add('error');
|
||||
$lexerElem.classList.add('error');
|
||||
setParsed(error, error);
|
||||
setScrollPercent(0);
|
||||
};
|
||||
}
|
||||
if (message.task !== 'defaults') {
|
||||
markedWorker.working = true;
|
||||
workerTimeout(0);
|
||||
}
|
||||
markedWorker.postMessage(message);
|
||||
}
|
||||
|
||||
function workerTimeout(seconds) {
|
||||
markedWorker.timeout = setTimeout(function() {
|
||||
seconds++;
|
||||
markedWorker.onerror('Marked has taken longer than ' + seconds + ' second' + (seconds > 1 ? 's' : '') + ' to respond...');
|
||||
workerTimeout(seconds);
|
||||
}, 1000);
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Marked Demo</title>
|
||||
<link rel="stylesheet" href="./demo.css" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a href="https://github.com/markedjs/marked">
|
||||
<img class="github-ribbon" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub">
|
||||
</a>
|
||||
|
||||
<header>
|
||||
<a href="../">
|
||||
<img src="../img/logo-black.svg" height="64px" width="64px" />
|
||||
</a>
|
||||
<h1>Marked Demo</h1>
|
||||
</header>
|
||||
|
||||
<div id="loading">Loading...</div>
|
||||
<div id="main">
|
||||
<div class="containers">
|
||||
<div class="container">
|
||||
<div class="label">
|
||||
<span>Input</span> ·
|
||||
<a id="permalink">Permalink</a> ·
|
||||
<span>Version: </span>
|
||||
<select id="markedVersion">
|
||||
<option value="pr">Add PR:</option>
|
||||
<option value="commit">Add Commit:</option>
|
||||
<option value="master" selected>master</option>
|
||||
</select>
|
||||
<input type="text" id="commitVersion" title="Press ENTER to add commit" />·
|
||||
<button id="clear">Clear</button>
|
||||
<select id="inputType">
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="options">Options</option>
|
||||
</select>
|
||||
</div>
|
||||
<textarea id="markdown" class="inputPane"></textarea>
|
||||
<textarea id="options" class="inputPane" placeholder="Options (as JSON)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="label">
|
||||
<select id="outputType">
|
||||
<option value="preview">Preview</option>
|
||||
<option value="html">HTML Source</option>
|
||||
<option value="lexer">Lexer Data</option>
|
||||
<option value="quickref">Quick Reference</option>
|
||||
</select> ·
|
||||
Response Time:
|
||||
<span id="responseTime"></span>
|
||||
</div>
|
||||
|
||||
<div id="preview" class="pane">
|
||||
<noscript>
|
||||
<h2>You'll need to enable Javascript to use this tool.</h2>
|
||||
</noscript>
|
||||
<iframe src="./preview.html" frameborder="0" sandbox="allow-same-origin allow-top-navigation-by-user-activation"></iframe>
|
||||
</div>
|
||||
|
||||
<textarea id="html" class="pane" readonly="readonly"></textarea>
|
||||
|
||||
<textarea id="lexer" class="pane" readonly="readonly"></textarea>
|
||||
|
||||
<textarea id="quickref" class="pane" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script id="markedCdn"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/unfetch/dist/unfetch.umd.js"></script>
|
||||
<script src="./demo.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
Marked - Markdown Parser
|
||||
========================
|
||||
|
||||
[Marked] lets you convert [Markdown] into HTML. Markdown is a simple text format whose goal is to be very easy to read and write, even when not converted to HTML. This demo page will let you type anything you like and see how it gets converted. Live. No more waiting around.
|
||||
|
||||
How To Use The Demo
|
||||
-------------------
|
||||
|
||||
1. Type in stuff on the left.
|
||||
2. See the live updates on the right.
|
||||
|
||||
That's it. Pretty simple. There's also a drop-down option in the upper right to switch between various views:
|
||||
|
||||
- **Preview:** A live display of the generated HTML as it would render in a browser.
|
||||
- **HTML Source:** The generated HTML before your browser makes it pretty.
|
||||
- **Lexer Data:** What [marked] uses internally, in case you like gory stuff like this.
|
||||
- **Quick Reference:** A brief run-down of how to format things using markdown.
|
||||
|
||||
Why Markdown?
|
||||
-------------
|
||||
|
||||
It's easy. It's not overly bloated, unlike HTML. Also, as the creator of [markdown] says,
|
||||
|
||||
> The overriding design goal for Markdown's
|
||||
> formatting syntax is to make it as readable
|
||||
> as possible. The idea is that a
|
||||
> Markdown-formatted document should be
|
||||
> publishable as-is, as plain text, without
|
||||
> looking like it's been marked up with tags
|
||||
> or formatting instructions.
|
||||
|
||||
Ready to start writing? Either start changing stuff on the left or
|
||||
[clear everything](/demo/?text=) with a simple click.
|
||||
|
||||
[Marked]: https://github.com/markedjs/marked/
|
||||
[Markdown]: http://daringfireball.net/projects/markdown/
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>marked.js preview</title>
|
||||
<link rel="stylesheet" href="./demo.css" />
|
||||
<base target="_parent">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
Markdown Quick Reference
|
||||
========================
|
||||
|
||||
This guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*.
|
||||
|
||||
[Markdown]: http://daringfireball.net/projects/markdown/
|
||||
|
||||
|
||||
Simple Text Formatting
|
||||
======================
|
||||
|
||||
First thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ do bold. ***Three together*** do ___both___.
|
||||
|
||||
Paragraphs are pretty easy too. Just have a blank line between chunks of text.
|
||||
|
||||
> This chunk of text is in a block quote. Its multiple lines will all be
|
||||
> indended a bit from the rest of the text.
|
||||
>
|
||||
> > Multiple levels of block quotes also work.
|
||||
|
||||
Sometimes you want to include some code, such as when you are explaining how `<h1>` HTML tags work, or maybe you are a programmer and you are discussing `someMethod()`.
|
||||
|
||||
If you want to include some code and have
|
||||
newlines preserved, indent the line with a tab
|
||||
or at least four spaces.
|
||||
Extra spaces work here too.
|
||||
This is also called preformatted text and it is useful for showing examples.
|
||||
The text will stay as text, so any *markdown* or <u>HTML</u> you add will
|
||||
not show up formatted. This way you can show markdown examples in a
|
||||
markdown document.
|
||||
|
||||
> You can also use preformatted text with your blockquotes
|
||||
> as long as you add at least five spaces.
|
||||
|
||||
|
||||
Headings
|
||||
========
|
||||
|
||||
There are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an "h1" style. Three or more hyphens under a line makes it "h2" (slightly smaller). You can also use multiple pound symbols before and after a heading. Pounds after the title are ignored. Here's some examples:
|
||||
|
||||
This is H1
|
||||
==========
|
||||
|
||||
This is H2
|
||||
----------
|
||||
|
||||
# This is H1
|
||||
## This is H2
|
||||
### This is H3 with some extra pounds ###
|
||||
#### You get the idea ####
|
||||
##### I don't need extra pounds at the end
|
||||
###### H6 is the max
|
||||
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
Let's link to a few sites. First, let's use the bare URL, like <http://www.github.com>. Great for text, but ugly for HTML.
|
||||
Next is an inline link to [Google](http://www.google.com). A little nicer.
|
||||
This is a reference-style link to [Wikipedia] [1].
|
||||
Lastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly.
|
||||
|
||||
[1]: http://www.wikipedia.org/
|
||||
[Yahoo]: http://www.yahoo.com/
|
||||
|
||||
Title attributes may be added to links by adding text after a link.
|
||||
This is the [inline link](http://www.bing.com "Bing") with a "Bing" title.
|
||||
You can also go to [W3C] [2] and maybe visit a [friend].
|
||||
|
||||
[2]: http://w3c.org (The W3C puts out specs for web-based things)
|
||||
[Friend]: http://facebook.com/ "Facebook!"
|
||||
|
||||
Email addresses in plain text are not linked: test@example.com.
|
||||
Email addresses wrapped in angle brackets are linked: <test@example.com>.
|
||||
They are also obfuscated so that email harvesting spam robots hopefully won't get them.
|
||||
|
||||
|
||||
Lists
|
||||
=====
|
||||
|
||||
* This is a bulleted list
|
||||
* Great for shopping lists
|
||||
- You can also use hyphens
|
||||
+ Or plus symbols
|
||||
|
||||
The above is an "unordered" list. Now, on for a bit of order.
|
||||
|
||||
1. Numbered lists are also easy
|
||||
2. Just start with a number
|
||||
3738762. However, the actual number doesn't matter when converted to HTML.
|
||||
1. This will still show up as 4.
|
||||
|
||||
You might want a few advanced lists:
|
||||
|
||||
- This top-level list is wrapped in paragraph tags
|
||||
- This generates an extra space between each top-level item.
|
||||
|
||||
- You do it by adding a blank line
|
||||
|
||||
- This nested list also has blank lines between the list items.
|
||||
|
||||
- How to create nested lists
|
||||
1. Start your regular list
|
||||
2. Indent nested lists with four spaces
|
||||
3. Further nesting means you should indent with four more spaces
|
||||
* This line is indented with eight spaces.
|
||||
|
||||
- List items can be quite lengthy. You can keep typing and either continue
|
||||
them on the next line with no indentation.
|
||||
|
||||
- Alternately, if that looks ugly, you can also
|
||||
indent the next line a bit for a prettier look.
|
||||
|
||||
- You can put large blocks of text in your list by just indenting with four spaces.
|
||||
|
||||
This is formatted the same as code, but you can inspect the HTML
|
||||
and find that it's just wrapped in a `<p>` tag and *won't* be shown
|
||||
as preformatted text.
|
||||
|
||||
You can keep adding more and more paragraphs to a single
|
||||
list item by adding the traditional blank line and then keep
|
||||
on indenting the paragraphs with four spaces. You really need
|
||||
to only indent the first line, but that looks ugly.
|
||||
|
||||
- Lists support blockquotes
|
||||
|
||||
> Just like this example here. By the way, you can
|
||||
> nest lists inside blockquotes!
|
||||
> - Fantastic!
|
||||
|
||||
- Lists support preformatted text
|
||||
|
||||
You just need to indent eight spaces.
|
||||
|
||||
|
||||
Even More
|
||||
=========
|
||||
|
||||
Horizontal Rule
|
||||
---------------
|
||||
|
||||
If you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.
|
||||
|
||||
---
|
||||
****************************
|
||||
_ _ _ _ _ _ _
|
||||
|
||||
Those three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
Images work exactly like links, but they have exclamation points in front. They work with references and titles too.
|
||||
|
||||
 and ![Happy].
|
||||
|
||||
[Happy]: http://www.wpclipart.com/smiley/simple_smiley/smiley_face_simple_green_small.png ("Smiley face")
|
||||
|
||||
|
||||
Inline HTML
|
||||
-----------
|
||||
|
||||
If markdown is too limiting, you can just insert your own <strike>crazy</strike> HTML. Span-level HTML <u>can *still* use markdown</u>. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.
|
||||
|
||||
<div style='font-family: "Comic Sans", sans-serif;'>
|
||||
It is a pity, but markdown does **not** work in here for most markdown parsers. [Marked] handles it pretty well.
|
||||
</div>
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare
|
||||
if (!self.Promise) {
|
||||
self.importScripts('https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.js');
|
||||
self.Promise = ES6Promise;
|
||||
}
|
||||
if (!self.fetch) {
|
||||
self.importScripts('https://cdn.jsdelivr.net/npm/unfetch/dist/unfetch.umd.js');
|
||||
self.fetch = unfetch;
|
||||
}
|
||||
|
||||
var versionCache = {};
|
||||
var currentVersion;
|
||||
|
||||
onunhandledrejection = function(e) {
|
||||
throw e.reason;
|
||||
};
|
||||
|
||||
onmessage = function(e) {
|
||||
if (e.data.version === currentVersion) {
|
||||
parse(e);
|
||||
} else {
|
||||
loadVersion(e.data.version).then(function() {
|
||||
parse(e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function parse(e) {
|
||||
switch (e.data.task) {
|
||||
case 'defaults':
|
||||
|
||||
var defaults = {};
|
||||
if (typeof marked.getDefaults === 'function') {
|
||||
defaults = marked.getDefaults();
|
||||
delete defaults.renderer;
|
||||
} else if ('defaults' in marked) {
|
||||
for (var prop in marked.defaults) {
|
||||
if (prop !== 'renderer') {
|
||||
defaults[prop] = marked.defaults[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
postMessage({
|
||||
task: e.data.task,
|
||||
defaults: defaults
|
||||
});
|
||||
break;
|
||||
case 'parse':
|
||||
var startTime = new Date();
|
||||
var lexed = marked.lexer(e.data.markdown, e.data.options);
|
||||
var lexedList = [];
|
||||
for (var i = 0; i < lexed.length; i++) {
|
||||
var lexedLine = [];
|
||||
for (var j in lexed[i]) {
|
||||
lexedLine.push(j + ':' + jsonString(lexed[i][j]));
|
||||
}
|
||||
lexedList.push('{' + lexedLine.join(', ') + '}');
|
||||
}
|
||||
var parsed = marked.parser(lexed, e.data.options);
|
||||
var endTime = new Date();
|
||||
// setTimeout(function () {
|
||||
postMessage({
|
||||
task: e.data.task,
|
||||
lexed: lexedList.join('\n'),
|
||||
parsed: parsed,
|
||||
time: endTime - startTime
|
||||
});
|
||||
// }, 10000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonString(input) {
|
||||
var output = (input + '')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\f/g, '\\f')
|
||||
.replace(/[\\"']/g, '\\$&')
|
||||
.replace(/\u0000/g, '\\0');
|
||||
return '"' + output + '"';
|
||||
};
|
||||
|
||||
function loadVersion(ver) {
|
||||
var promise;
|
||||
if (versionCache[ver]) {
|
||||
promise = Promise.resolve(versionCache[ver]);
|
||||
} else {
|
||||
promise = fetch(ver)
|
||||
.then(function(res) { return res.text(); })
|
||||
.then(function(text) {
|
||||
versionCache[ver] = text;
|
||||
return text;
|
||||
});
|
||||
}
|
||||
return promise.then(function(text) {
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
Function(text)();
|
||||
} catch (err) {
|
||||
throw new Error('Cannot load that version of marked');
|
||||
}
|
||||
currentVersion = ver;
|
||||
});
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="996px" height="439.589px" viewBox="0 0 996 439.589" enable-background="new 0 0 996 439.589" xml:space="preserve">
|
||||
<g>
|
||||
<g id="Marked_Letters">
|
||||
<path fill="#010101" d="M185.044,358.869h-9.961l-1.143-25.986l-14.092,25.986h-6.24l-6.709-26.807l-8.818,26.807h-10.225
|
||||
l13.389-40.605h10.928l6.416,25.781l13.916-25.781h10.635L185.044,358.869z"/>
|
||||
<path fill="#010101" d="M226.617,358.869h-10.811l-1.084-8.994H202.33l-4.102,8.994h-10.605l20.566-40.605h10.986L226.617,358.869
|
||||
z M214.019,344.162l-1.934-15.527l-7.061,15.527H214.019z"/>
|
||||
<path fill="#010101" d="M267.712,358.869h-11.338l-4.043-8.467c-1.191-2.559-2.227-4.478-3.105-5.757s-1.651-2.07-2.314-2.373
|
||||
c-0.664-0.303-1.553-0.454-2.666-0.454h-1.553l-2.578,17.051h-10.283l5.889-40.605h15.85c5.761,0,9.672,1.27,11.733,3.809
|
||||
c2.061,2.539,3.091,5.283,3.091,8.232c0,3.105-0.854,5.522-2.563,7.251c-1.709,1.729-4.097,2.798-7.163,3.208
|
||||
c1.562,0.898,2.769,1.953,3.618,3.164s1.86,3.096,3.032,5.654L267.712,358.869z M255.525,330.012c0-1.562-0.571-2.925-1.714-4.087
|
||||
s-3.453-1.743-6.929-1.743h-1.611l-1.699,11.719h4.102c2.89,0,4.922-0.61,6.094-1.831S255.525,331.496,255.525,330.012z"/>
|
||||
<path fill="#010101" d="M313.705,318.264l-17.578,18.223l14.912,22.383h-11.807l-14.59-21.65l-3.282,21.65h-10.342l6.064-40.605
|
||||
h10.401l-2.578,17.256l16.611-17.256H313.705z"/>
|
||||
<path fill="#010101" d="M346.426,318.264L345.4,325.5h-16.436l-1.26,8.818h14.268l-1.025,7.266h-14.385l-1.377,10.02h19.98
|
||||
l-1.025,7.266h-30.439l5.889-40.605H346.426z"/>
|
||||
<path fill="#010101" d="M389.953,338.156c0,4.512-0.86,8.301-2.578,11.367c-1.719,3.066-4.273,5.391-7.662,6.973
|
||||
s-7.583,2.373-12.582,2.373H348.41l5.83-40.605h14.941c3.534,0,6.625,0.527,9.271,1.582s4.82,2.51,6.52,4.365
|
||||
s2.953,3.97,3.764,6.343S389.953,335.461,389.953,338.156z M379.377,338.303c0-2.031-0.387-4.004-1.158-5.918
|
||||
s-2.192-3.54-4.262-4.878c-2.07-1.338-4.941-2.007-8.613-2.007h-1.67l-3.779,26.104h5.449c2.597,0,4.828-0.391,6.693-1.172
|
||||
s3.33-1.821,4.395-3.12s1.821-2.71,2.271-4.233C379.152,341.555,379.377,339.963,379.377,338.303z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#010101" d="M406.922,307.149l-3.703,26.35c-0.609,4.324-2.72,6.486-6.33,6.486c-0.773,0-1.553-0.07-2.338-0.211
|
||||
l-0.756-3.023c0.879,0.188,1.67,0.281,2.373,0.281c1.219,0,2.118-0.302,2.698-0.905s0.964-1.55,1.151-2.839l3.674-26.139H406.922z
|
||||
"/>
|
||||
<path fill="#010101" d="M423.027,307.8l-0.475,3.357c-1.312-0.867-2.748-1.301-4.307-1.301c-1.312,0-2.361,0.34-3.146,1.02
|
||||
s-1.178,1.576-1.178,2.689c0,0.551,0.188,1.131,0.562,1.74s1.471,1.644,3.287,3.103s3.015,2.739,3.595,3.841
|
||||
s0.87,2.203,0.87,3.305c0,1.758-0.662,3.275-1.986,4.553s-3.088,1.916-5.291,1.916c-1.957,0-3.938-0.586-5.941-1.758l0.51-3.604
|
||||
c2.238,1.559,4.16,2.338,5.766,2.338c0.867,0,1.69-0.284,2.47-0.852c0.779-0.568,1.169-1.339,1.169-2.312
|
||||
c0-0.62-0.197-1.265-0.59-1.933c-0.394-0.667-1.502-1.745-3.324-3.232c-1.824-1.486-3.014-2.778-3.568-3.874
|
||||
c-0.555-1.097-0.832-2.185-0.832-3.263c0-1.899,0.785-3.491,2.355-4.775c1.57-1.283,3.422-1.926,5.555-1.926
|
||||
C420.016,306.833,421.516,307.155,423.027,307.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#010101" d="M357,200v-92c0-8.284-6.716-15-15-15H172c-8.284,0-15,6.716-15,15v92H357z"/>
|
||||
<path fill="#010101" d="M157,205v73c0,8.284,6.716,15,15,15h170c8.284,0,15-6.716,15-15v-73H157z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M208.737,180.15v-33.125l16.987,21.234l16.987-21.234v33.125h16.987v-57.756h-16.987l-16.987,21.234
|
||||
l-16.987-21.234H191.75v57.756H208.737z"/>
|
||||
<path fill="#FFFFFF" d="M322.55,151.272h-16.987v-28.878h-16.986v28.878h-16.987L297.069,181L322.55,151.272z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M217,241.154c0,2.124-1.722,3.846-3.845,3.846h-5.31c-2.124,0-3.845-1.722-3.845-3.846v-0.309
|
||||
c0-2.124,1.722-3.846,3.845-3.846h5.31c2.124,0,3.845,1.722,3.845,3.846V241.154z"/>
|
||||
<path fill="#FFFFFF" d="M207.859,256.5c0,1.933-1.1,3.5-2.457,3.5h-12.046c-1.357,0-2.457-1.567-2.457-3.5l0,0
|
||||
c0-1.933,1.1-3.5,2.457-3.5h12.046C206.759,253,207.859,254.567,207.859,256.5L207.859,256.5z"/>
|
||||
<path fill="#FFFFFF" d="M328.044,238.021c-1.058-3.094-2.692-5.85-4.907-8.269c-2.215-2.418-5.049-4.314-8.498-5.689
|
||||
c-3.45-1.375-7.479-2.063-12.086-2.063h-19.477l-7.6,52.932h24.403c6.518,0,11.984-1.032,16.402-3.094
|
||||
c4.416-2.062,7.746-5.092,9.986-9.09c2.24-3.996,3.361-8.936,3.361-14.817C329.629,244.418,329.1,241.115,328.044,238.021z
|
||||
M314.964,254.347c-0.586,1.986-1.573,3.826-2.96,5.519c-1.389,1.694-3.297,3.049-5.729,4.067s-5.34,1.528-8.727,1.528h-7.103
|
||||
l4.927-34.027h2.176c4.787,0,8.529,0.872,11.229,2.616c2.698,1.744,4.55,3.863,5.557,6.358c1.005,2.494,1.508,5.066,1.508,7.713
|
||||
C315.842,250.286,315.549,252.361,314.964,254.347z"/>
|
||||
<path fill="#FFFFFF" d="M246.148,241.043l1.373-9.61h21.424l1.336-9.434h-10.601h-24.38h-1.256c-2.125,0-3.846,1.877-3.846,4
|
||||
s1.721,4,3.846,4h0.096h3.793c1.928,0,3.491,1.563,3.491,3.491c0,1.928-1.563,3.491-3.491,3.491h-4.806L233.126,237h-8.983
|
||||
c-1.299,0-2.353,1.786-2.353,3.988c0,2.203,1.053,3.989,2.353,3.989h7.826L231.966,245h3.189c2.124,0,3.845,1.722,3.845,3.846
|
||||
v0.309c0,2.124-1.722,3.846-3.845,3.846h-4.349h-0.96H216.2c-1.933,0-3.5,1.567-3.5,3.5s1.567,3.5,3.5,3.5h8.645h4.945h0.364
|
||||
c2.124,0,3.845,1.722,3.845,3.846v0.309c0,2.124-1.722,3.846-3.845,3.846h-1.524h-3.785h-1.8c-2.125,0-3.846,1.377-3.846,3.5
|
||||
s1.721,3.5,3.846,3.5h25.636c0.273,0,0.538-0.024,0.795-0.068h17.828l1.336-9.471h-26.045l1.795-13.062h18.751l1.336-9.471
|
||||
h-18.598l0.029-0.205C246.055,242.213,246.144,241.647,246.148,241.043z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g id="Marked_Letters_1_">
|
||||
<path fill="#010101" d="M619.045,356.869h-9.961l-1.143-25.986l-14.092,25.986h-6.24l-6.709-26.807l-8.818,26.807h-10.225
|
||||
l13.389-40.605h10.928l6.416,25.781l13.916-25.781h10.635L619.045,356.869z"/>
|
||||
<path fill="#010101" d="M660.617,356.869h-10.811l-1.084-8.994H636.33l-4.102,8.994h-10.605l20.566-40.605h10.986L660.617,356.869
|
||||
z M648.02,342.162l-1.934-15.527l-7.061,15.527H648.02z"/>
|
||||
<path fill="#010101" d="M701.713,356.869h-11.338l-4.043-8.467c-1.191-2.559-2.227-4.478-3.105-5.757s-1.651-2.07-2.314-2.373
|
||||
c-0.664-0.303-1.553-0.454-2.666-0.454h-1.553l-2.578,17.051h-10.283l5.889-40.605h15.85c5.761,0,9.672,1.27,11.732,3.809
|
||||
s3.092,5.283,3.092,8.232c0,3.105-0.855,5.522-2.564,7.251s-4.097,2.798-7.162,3.208c1.562,0.898,2.768,1.953,3.617,3.164
|
||||
s1.861,3.096,3.033,5.654L701.713,356.869z M689.525,328.012c0-1.562-0.572-2.925-1.715-4.087s-3.452-1.743-6.928-1.743h-1.611
|
||||
l-1.699,11.719h4.102c2.89,0,4.922-0.61,6.094-1.831S689.525,329.496,689.525,328.012z"/>
|
||||
<path fill="#010101" d="M747.705,316.264l-17.578,18.223l14.912,22.383h-11.807l-14.59-21.65l-3.281,21.65H705.02l6.064-40.605
|
||||
h10.4l-2.578,17.256l16.611-17.256H747.705z"/>
|
||||
<path fill="#010101" d="M780.426,316.264L779.4,323.5h-16.436l-1.26,8.818h14.268l-1.025,7.266h-14.385l-1.377,10.02h19.98
|
||||
l-1.025,7.266h-30.439l5.889-40.605H780.426z"/>
|
||||
<path fill="#010101" d="M823.953,336.156c0,4.512-0.86,8.301-2.578,11.367c-1.719,3.066-4.273,5.391-7.662,6.973
|
||||
s-7.583,2.373-12.582,2.373H782.41l5.83-40.605h14.941c3.534,0,6.625,0.527,9.271,1.582s4.82,2.51,6.52,4.365
|
||||
s2.953,3.97,3.764,6.343S823.953,333.461,823.953,336.156z M813.377,336.303c0-2.031-0.387-4.004-1.158-5.918
|
||||
s-2.192-3.54-4.262-4.878c-2.07-1.338-4.941-2.007-8.613-2.007h-1.67l-3.779,26.104h5.449c2.597,0,4.828-0.391,6.693-1.172
|
||||
s3.33-1.821,4.395-3.12s1.821-2.71,2.271-4.233C813.152,339.555,813.377,337.963,813.377,336.303z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#010101" d="M840.922,305.149l-3.703,26.35c-0.609,4.324-2.72,6.486-6.33,6.486c-0.773,0-1.553-0.07-2.338-0.211
|
||||
l-0.756-3.023c0.879,0.188,1.67,0.281,2.373,0.281c1.219,0,2.118-0.302,2.698-0.905s0.964-1.55,1.151-2.839l3.674-26.139H840.922z
|
||||
"/>
|
||||
<path fill="#010101" d="M857.027,305.8l-0.475,3.357c-1.312-0.867-2.748-1.301-4.307-1.301c-1.312,0-2.361,0.34-3.146,1.02
|
||||
s-1.178,1.576-1.178,2.689c0,0.551,0.188,1.131,0.562,1.74s1.471,1.644,3.287,3.103s3.015,2.739,3.595,3.841
|
||||
s0.87,2.203,0.87,3.305c0,1.758-0.662,3.275-1.986,4.553s-3.088,1.916-5.291,1.916c-1.957,0-3.938-0.586-5.941-1.758l0.51-3.604
|
||||
c2.238,1.559,4.16,2.338,5.766,2.338c0.867,0,1.69-0.284,2.47-0.852c0.779-0.568,1.169-1.339,1.169-2.312
|
||||
c0-0.62-0.197-1.265-0.59-1.933c-0.394-0.667-1.502-1.745-3.324-3.232c-1.824-1.486-3.014-2.778-3.568-3.874
|
||||
c-0.555-1.097-0.832-2.185-0.832-3.263c0-1.899,0.785-3.491,2.355-4.775c1.57-1.283,3.422-1.926,5.555-1.926
|
||||
C854.016,304.833,855.516,305.155,857.027,305.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M641,234.154c0,2.124-1.722,3.846-3.846,3.846h-5.309c-2.124,0-3.846-1.722-3.846-3.846v-0.309
|
||||
c0-2.124,1.722-3.846,3.846-3.846h5.309c2.124,0,3.846,1.722,3.846,3.846V234.154z"/>
|
||||
<path d="M631.859,249.5c0,1.933-1.1,3.5-2.457,3.5h-12.046c-1.356,0-2.456-1.567-2.456-3.5l0,0c0-1.933,1.1-3.5,2.456-3.5h12.046
|
||||
C630.76,246,631.859,247.567,631.859,249.5L631.859,249.5z"/>
|
||||
<path d="M752.044,231.021c-1.058-3.094-2.692-5.85-4.907-8.269c-2.215-2.418-5.049-4.314-8.498-5.689
|
||||
c-3.45-1.375-7.479-2.063-12.086-2.063h-19.477l-7.6,52.932h24.402c6.518,0,11.984-1.032,16.402-3.094
|
||||
c4.416-2.062,7.746-5.092,9.986-9.09c2.24-3.996,3.361-8.936,3.361-14.817C753.629,237.418,753.1,234.115,752.044,231.021z
|
||||
M738.964,247.347c-0.586,1.986-1.573,3.826-2.96,5.519c-1.389,1.694-3.297,3.049-5.729,4.067s-5.34,1.528-8.727,1.528h-7.103
|
||||
l4.927-34.027h2.176c4.787,0,8.529,0.872,11.229,2.616c2.698,1.744,4.55,3.863,5.557,6.358c1.005,2.494,1.508,5.066,1.508,7.713
|
||||
C739.842,243.286,739.549,245.361,738.964,247.347z"/>
|
||||
<path d="M670.148,234.043l1.373-9.61h21.424l1.337-9.434h-10.601h-24.381h-1.256c-2.124,0-3.846,1.877-3.846,4s1.722,4,3.846,4
|
||||
h0.096h3.793c1.929,0,3.492,1.563,3.492,3.491c0,1.928-1.563,3.491-3.492,3.491h-4.805L657.126,230h-8.983
|
||||
c-1.299,0-2.352,1.786-2.352,3.988c0,2.203,1.053,3.989,2.352,3.989h7.826L655.966,238h3.188c2.124,0,3.846,1.722,3.846,3.846
|
||||
v0.309c0,2.124-1.722,3.846-3.846,3.846h-4.349h-0.96H640.2c-1.933,0-3.5,1.567-3.5,3.5s1.567,3.5,3.5,3.5h8.646h4.945h0.363
|
||||
c2.124,0,3.846,1.722,3.846,3.846v0.309c0,2.124-1.722,3.846-3.846,3.846h-1.523h-3.785h-1.801c-2.124,0-3.846,1.377-3.846,3.5
|
||||
s1.722,3.5,3.846,3.5h25.637c0.272,0,0.537-0.024,0.795-0.068h17.827l1.337-9.471h-26.045l1.795-13.062h18.75l1.337-9.471
|
||||
h-18.599l0.029-0.205C670.055,235.213,670.144,234.647,670.148,234.043z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#010101" d="M642.236,179.15v-33.125l16.988,21.234l16.986-21.234v33.125h16.987v-57.756h-16.987l-16.986,21.234
|
||||
l-16.988-21.234H625.25v57.756H642.236z"/>
|
||||
<path fill="#010101" d="M756.05,150.272h-16.987v-28.878h-16.986v28.878H705.09L730.569,180L756.05,150.272z"/>
|
||||
</g>
|
||||
<path fill="#010101" d="M776,90H606c-11.028,0-20,8.972-20,20v170c0,11.028,8.972,20,20,20h170c11.028,0,20-8.972,20-20V110
|
||||
C796,98.972,787.028,90,776,90z M606,100h170c5.514,0,10,4.486,10,10v83H596v-83C596,104.486,600.486,100,606,100z M776,290H606
|
||||
c-5.514,0-10-4.486-10-10v-82h190v82C786,285.514,781.514,290,776,290z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
|
@ -1,32 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="300px" height="439.589px" viewBox="100 100 400 400" xml:space="preserve">
|
||||
<g>
|
||||
<g id="Marked_Letters">
|
||||
<path fill="#010101" d="M185.044,358.869h-9.961l-1.143-25.986l-14.092,25.986h-6.24l-6.709-26.807l-8.818,26.807h-10.225 l13.389-40.605h10.928l6.416,25.781l13.916-25.781h10.635L185.044,358.869z"/>
|
||||
<path fill="#010101" d="M226.617,358.869h-10.811l-1.084-8.994H202.33l-4.102,8.994h-10.605l20.566-40.605h10.986L226.617,358.869 z M214.019,344.162l-1.934-15.527l-7.061,15.527H214.019z"/>
|
||||
<path fill="#010101" d="M267.712,358.869h-11.338l-4.043-8.467c-1.191-2.559-2.227-4.478-3.105-5.757s-1.651-2.07-2.314-2.373 c-0.664-0.303-1.553-0.454-2.666-0.454h-1.553l-2.578,17.051h-10.283l5.889-40.605h15.85c5.761,0,9.672,1.27,11.733,3.809 c2.061,2.539,3.091,5.283,3.091,8.232c0,3.105-0.854,5.522-2.563,7.251c-1.709,1.729-4.097,2.798-7.163,3.208 c1.562,0.898,2.769,1.953,3.618,3.164s1.86,3.096,3.032,5.654L267.712,358.869z M255.525,330.012c0-1.562-0.571-2.925-1.714-4.087 s-3.453-1.743-6.929-1.743h-1.611l-1.699,11.719h4.102c2.89,0,4.922-0.61,6.094-1.831S255.525,331.496,255.525,330.012z"/>
|
||||
<path fill="#010101" d="M313.705,318.264l-17.578,18.223l14.912,22.383h-11.807l-14.59-21.65l-3.282,21.65h-10.342l6.064-40.605 h10.401l-2.578,17.256l16.611-17.256H313.705z"/>
|
||||
<path fill="#010101" d="M346.426,318.264L345.4,325.5h-16.436l-1.26,8.818h14.268l-1.025,7.266h-14.385l-1.377,10.02h19.98 l-1.025,7.266h-30.439l5.889-40.605H346.426z"/>
|
||||
<path fill="#010101" d="M389.953,338.156c0,4.512-0.86,8.301-2.578,11.367c-1.719,3.066-4.273,5.391-7.662,6.973 s-7.583,2.373-12.582,2.373H348.41l5.83-40.605h14.941c3.534,0,6.625,0.527,9.271,1.582s4.82,2.51,6.52,4.365 s2.953,3.97,3.764,6.343S389.953,335.461,389.953,338.156z M379.377,338.303c0-2.031-0.387-4.004-1.158-5.918 s-2.192-3.54-4.262-4.878c-2.07-1.338-4.941-2.007-8.613-2.007h-1.67l-3.779,26.104h5.449c2.597,0,4.828-0.391,6.693-1.172 s3.33-1.821,4.395-3.12s1.821-2.71,2.271-4.233C379.152,341.555,379.377,339.963,379.377,338.303z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#010101" d="M406.922,307.149l-3.703,26.35c-0.609,4.324-2.72,6.486-6.33,6.486c-0.773,0-1.553-0.07-2.338-0.211 l-0.756-3.023c0.879,0.188,1.67,0.281,2.373,0.281c1.219,0,2.118-0.302,2.698-0.905s0.964-1.55,1.151-2.839l3.674-26.139H406.922z "/>
|
||||
<path fill="#010101" d="M423.027,307.8l-0.475,3.357c-1.312-0.867-2.748-1.301-4.307-1.301c-1.312,0-2.361,0.34-3.146,1.02 s-1.178,1.576-1.178,2.689c0,0.551,0.188,1.131,0.562,1.74s1.471,1.644,3.287,3.103s3.015,2.739,3.595,3.841 s0.87,2.203,0.87,3.305c0,1.758-0.662,3.275-1.986,4.553s-3.088,1.916-5.291,1.916c-1.957,0-3.938-0.586-5.941-1.758l0.51-3.604 c2.238,1.559,4.16,2.338,5.766,2.338c0.867,0,1.69-0.284,2.47-0.852c0.779-0.568,1.169-1.339,1.169-2.312 c0-0.62-0.197-1.265-0.59-1.933c-0.394-0.667-1.502-1.745-3.324-3.232c-1.824-1.486-3.014-2.778-3.568-3.874 c-0.555-1.097-0.832-2.185-0.832-3.263c0-1.899,0.785-3.491,2.355-4.775c1.57-1.283,3.422-1.926,5.555-1.926 C420.016,306.833,421.516,307.155,423.027,307.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#010101" d="M357,200v-92c0-8.284-6.716-15-15-15H172c-8.284,0-15,6.716-15,15v92H357z"/>
|
||||
<path fill="#010101" d="M157,205v73c0,8.284,6.716,15,15,15h170c8.284,0,15-6.716,15-15v-73H157z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M208.737,180.15v-33.125l16.987,21.234l16.987-21.234v33.125h16.987v-57.756h-16.987l-16.987,21.234 l-16.987-21.234H191.75v57.756H208.737z"/>
|
||||
<path fill="#FFFFFF" d="M322.55,151.272h-16.987v-28.878h-16.986v28.878h-16.987L297.069,181L322.55,151.272z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M217,241.154c0,2.124-1.722,3.846-3.845,3.846h-5.31c-2.124,0-3.845-1.722-3.845-3.846v-0.309 c0-2.124,1.722-3.846,3.845-3.846h5.31c2.124,0,3.845,1.722,3.845,3.846V241.154z"/>
|
||||
<path fill="#FFFFFF" d="M207.859,256.5c0,1.933-1.1,3.5-2.457,3.5h-12.046c-1.357,0-2.457-1.567-2.457-3.5l0,0 c0-1.933,1.1-3.5,2.457-3.5h12.046C206.759,253,207.859,254.567,207.859,256.5L207.859,256.5z"/>
|
||||
<path fill="#FFFFFF" d="M328.044,238.021c-1.058-3.094-2.692-5.85-4.907-8.269c-2.215-2.418-5.049-4.314-8.498-5.689 c-3.45-1.375-7.479-2.063-12.086-2.063h-19.477l-7.6,52.932h24.403c6.518,0,11.984-1.032,16.402-3.094 c4.416-2.062,7.746-5.092,9.986-9.09c2.24-3.996,3.361-8.936,3.361-14.817C329.629,244.418,329.1,241.115,328.044,238.021z M314.964,254.347c-0.586,1.986-1.573,3.826-2.96,5.519c-1.389,1.694-3.297,3.049-5.729,4.067s-5.34,1.528-8.727,1.528h-7.103 l4.927-34.027h2.176c4.787,0,8.529,0.872,11.229,2.616c2.698,1.744,4.55,3.863,5.557,6.358c1.005,2.494,1.508,5.066,1.508,7.713 C315.842,250.286,315.549,252.361,314.964,254.347z"/>
|
||||
<path fill="#FFFFFF" d="M246.148,241.043l1.373-9.61h21.424l1.336-9.434h-10.601h-24.38h-1.256c-2.125,0-3.846,1.877-3.846,4 s1.721,4,3.846,4h0.096h3.793c1.928,0,3.491,1.563,3.491,3.491c0,1.928-1.563,3.491-3.491,3.491h-4.806L233.126,237h-8.983 c-1.299,0-2.353,1.786-2.353,3.988c0,2.203,1.053,3.989,2.353,3.989h7.826L231.966,245h3.189c2.124,0,3.845,1.722,3.845,3.846 v0.309c0,2.124-1.722,3.846-3.845,3.846h-4.349h-0.96H216.2c-1.933,0-3.5,1.567-3.5,3.5s1.567,3.5,3.5,3.5h8.645h4.945h0.364 c2.124,0,3.845,1.722,3.845,3.846v0.309c0,2.124-1.722,3.846-3.845,3.846h-1.524h-3.785h-1.8c-2.125,0-3.846,1.377-3.846,3.5 s1.721,3.5,3.846,3.5h25.636c0.273,0,0.538-0.024,0.795-0.068h17.828l1.336-9.471h-26.045l1.795-13.062h18.751l1.336-9.471 h-18.598l0.029-0.205C246.055,242.213,246.144,241.647,246.148,241.043z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.5 KiB |
|
|
@ -1,270 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Marked.js Documentation</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
background: #F9F9F9;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#content h1:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
nav {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
background: white;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
nav > ul {
|
||||
position: sticky;
|
||||
top: 5px;
|
||||
margin: 10px 0px;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
nav > ul > li {
|
||||
min-width: 125px;
|
||||
padding: 0px 15px;
|
||||
}
|
||||
|
||||
nav > ul > li > ul {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
nav > ul > li > ul > li {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
nav .selected {
|
||||
color: #111;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav .selected:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
header h1 { margin: 0; }
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
code:not([class]) {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://github.com/markedjs/marked" class="github-corner" aria-label="View source on Github">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#202020; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
|
||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div id="container">
|
||||
<header>
|
||||
<a href="#/README.md">
|
||||
<img src="img/logo-black.svg" height="64px" width="64px" />
|
||||
</a>
|
||||
<h1>Marked.js Documentation</h1>
|
||||
</header>
|
||||
<div style="display: flex">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#/README.md">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#/README.md#demo">Demo</a></li>
|
||||
<li><a href="#/README.md#installation">Installation</a></li>
|
||||
<li><a href="#/README.md#usage">Usage</a></li>
|
||||
<li><a href="#/README.md#specifications">Specs</a></li>
|
||||
<li><a href="#/README.md#security">Security</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/USING_ADVANCED.md">Advanced Usage</a>
|
||||
<ul>
|
||||
<li><a href="#/USING_ADVANCED.md#options">Options</a></li>
|
||||
<li><a href="#/USING_ADVANCED.md#highlight">Highlighting</a></li>
|
||||
<li><a href="#/USING_ADVANCED.md#workers">Workers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/USING_PRO.md">Extensibility</a>
|
||||
<ul>
|
||||
<li><a href="#/USING_PRO.md#renderer">Renderer</a></li>
|
||||
<li><a href="#/USING_PRO.md#lexer">Lexer</a></li>
|
||||
<li><a href="#/USING_PRO.md#parser">Parser</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/CONTRIBUTING.md">Contributing</a>
|
||||
<ul>
|
||||
<li><a href="#/CONTRIBUTING.md#design-principles">Design Principles</a></li>
|
||||
<li><a href="#/CONTRIBUTING.md#priorities">Priorities</a></li>
|
||||
<li><a href="#/CONTRIBUTING.md#test-early-often-and-everything">Testing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#/CODE_OF_CONDUCT.md">Code of Conduct</a></li>
|
||||
<li><a href="#/AUTHORS.md">Authors</a></li>
|
||||
<li><a href="#/PUBLISHING.md">Publishing</a></li>
|
||||
<li><a href="https://github.com/markedjs/marked/blob/master/LICENSE.md">License</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/unfetch/dist/unfetch.umd.js"></script>
|
||||
<script>
|
||||
if (!window.Promise) {
|
||||
window.Promise = ES6Promise;
|
||||
}
|
||||
if (!window.fetch) {
|
||||
window.fetch = unfetch;
|
||||
}
|
||||
|
||||
var content = document.querySelector('#content');
|
||||
var body = document.querySelector('html');
|
||||
var navLinks = document.querySelectorAll('nav a');
|
||||
var currentPage = 'README.md';
|
||||
var currentHash = '';
|
||||
var renderedPage = '';
|
||||
|
||||
function hashChange() {
|
||||
var hash = location.hash.slice(1);
|
||||
if (!hash) {
|
||||
hash = 'README.md';
|
||||
}
|
||||
|
||||
var uri = hash.split('#');
|
||||
|
||||
if (uri[0].match(/^\//)) {
|
||||
currentPage = uri[0].slice(1);
|
||||
if (uri.length > 1) {
|
||||
currentHash = uri[1];
|
||||
} else {
|
||||
currentHash = '';
|
||||
}
|
||||
} else {
|
||||
currentHash = uri[0];
|
||||
}
|
||||
|
||||
fetchPage(currentPage).then(function () {
|
||||
fetchAnchor(currentHash)
|
||||
});
|
||||
|
||||
var url = '#/' + currentPage + (currentHash ? '#' + currentHash : '');
|
||||
var fullUrl = window.location.origin + '/' + url;
|
||||
|
||||
navLinks.forEach(function(link) {
|
||||
link.className = link.href === fullUrl ? 'selected' : '';
|
||||
});
|
||||
|
||||
history.replaceState('', document.title, url);
|
||||
}
|
||||
|
||||
function fetchAnchor(anchor) {
|
||||
if (!anchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hashElement = document.getElementById(anchor);
|
||||
if (hashElement) {
|
||||
hashElement.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchPage(page) {
|
||||
if (page === renderedPage) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return fetch(page)
|
||||
.then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('Error ' + res.status + ': ' + res.statusText);
|
||||
}
|
||||
return res.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
renderedPage = page;
|
||||
content.innerHTML = marked(text);
|
||||
body.scrollTop = 0;
|
||||
}).catch(function (e) {
|
||||
content.innerHTML = '<p>Oops! There was a problem rendering the page.</p>'
|
||||
+ '<p>' + e.message + '</p>';
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', function (e) {
|
||||
e.preventDefault();
|
||||
hashChange();
|
||||
});
|
||||
|
||||
hashChange();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,111 +0,0 @@
|
|||
.ds q \N'34'
|
||||
.TH marked 1
|
||||
|
||||
.SH NAME
|
||||
marked \- a javascript markdown parser
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B marked
|
||||
[\-o \fI<output>\fP] [\-i \fI<input>\fP] [\-\-help]
|
||||
[\-\-tokens] [\-\-pedantic] [\-\-gfm]
|
||||
[\-\-breaks] [\-\-sanitize]
|
||||
[\-\-smart\-lists] [\-\-lang\-prefix \fI<prefix>\fP]
|
||||
[\-\-no\-etc...] [\-\-silent] [\fIfilename\fP]
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B marked
|
||||
is a full-featured javascript markdown parser, built for speed.
|
||||
It also includes multiple GFM features.
|
||||
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
cat in.md | marked > out.html
|
||||
.TP
|
||||
echo "hello *world*" | marked
|
||||
.TP
|
||||
marked \-o out.html \-i in.md \-\-gfm
|
||||
.TP
|
||||
marked \-\-output="hello world.html" \-i in.md \-\-no-breaks
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BI \-o,\ \-\-output\ [\fIoutput\fP]
|
||||
Specify file output. If none is specified, write to stdout.
|
||||
.TP
|
||||
.BI \-i,\ \-\-input\ [\fIinput\fP]
|
||||
Specify file input, otherwise use last argument as input file.
|
||||
If no input file is specified, read from stdin.
|
||||
.TP
|
||||
.BI \-\-test
|
||||
Makes sure the test(s) pass.
|
||||
.RS
|
||||
.PP
|
||||
.B \-\-glob [\fIfile\fP]
|
||||
Specify which test to use.
|
||||
.PP
|
||||
.B \-\-fix
|
||||
Fixes tests.
|
||||
.PP
|
||||
.B \-\-bench
|
||||
Benchmarks the test(s).
|
||||
.PP
|
||||
.B \-\-time
|
||||
Times The test(s).
|
||||
.PP
|
||||
.B \-\-minified
|
||||
Runs test file(s) as minified.
|
||||
.PP
|
||||
.B \-\-stop
|
||||
Stop process if a test fails.
|
||||
.RE
|
||||
.TP
|
||||
.BI \-t,\ \-\-tokens
|
||||
Output a token stream instead of html.
|
||||
.TP
|
||||
.BI \-\-pedantic
|
||||
Conform to obscure parts of markdown.pl as much as possible.
|
||||
Don't fix original markdown bugs.
|
||||
.TP
|
||||
.BI \-\-gfm
|
||||
Enable github flavored markdown.
|
||||
.TP
|
||||
.BI \-\-breaks
|
||||
Enable GFM line breaks. Only works with the gfm option.
|
||||
.TP
|
||||
.BI \-\-sanitize
|
||||
Sanitize output. Ignore any HTML input.
|
||||
.TP
|
||||
.BI \-\-smart\-lists
|
||||
Use smarter list behavior than the original markdown.
|
||||
.TP
|
||||
.BI \-\-lang\-prefix\ [\fIprefix\fP]
|
||||
Set the prefix for code block classes.
|
||||
.TP
|
||||
.BI \-\-mangle
|
||||
Mangle email addresses.
|
||||
.TP
|
||||
.BI \-\-no\-sanitize,\ \-no-etc...
|
||||
The inverse of any of the marked options above.
|
||||
.TP
|
||||
.BI \-\-silent
|
||||
Silence error output.
|
||||
.TP
|
||||
.BI \-h,\ \-\-help
|
||||
Display help information.
|
||||
|
||||
.SH CONFIGURATION
|
||||
For configuring and running programmatically.
|
||||
|
||||
.B Example
|
||||
|
||||
require('marked')('*foo*', { gfm: true });
|
||||
|
||||
.SH BUGS
|
||||
Please report any bugs to https://github.com/markedjs/marked.
|
||||
|
||||
.SH LICENSE
|
||||
Copyright (c) 2011-2014, Christopher Jeffrey (MIT License).
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR markdown(1),
|
||||
.BR node.js(1)
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
marked(1) General Commands Manual marked(1)
|
||||
|
||||
NAME
|
||||
marked - a javascript markdown parser
|
||||
|
||||
SYNOPSIS
|
||||
marked [-o <output>] [-i <input>] [--help] [--tokens] [--pedantic]
|
||||
[--gfm] [--breaks] [--sanitize] [--smart-lists] [--lang-prefix <pre-
|
||||
fix>] [--no-etc...] [--silent] [filename]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
marked is a full-featured javascript markdown parser, built for speed.
|
||||
It also includes multiple GFM features.
|
||||
|
||||
EXAMPLES
|
||||
cat in.md | marked > out.html
|
||||
|
||||
echo "hello *world*" | marked
|
||||
|
||||
marked -o out.html -i in.md --gfm
|
||||
|
||||
marked --output="hello world.html" -i in.md --no-breaks
|
||||
|
||||
OPTIONS
|
||||
-o, --output [output]
|
||||
Specify file output. If none is specified, write to stdout.
|
||||
|
||||
-i, --input [input]
|
||||
Specify file input, otherwise use last argument as input file.
|
||||
If no input file is specified, read from stdin.
|
||||
|
||||
--test Makes sure the test(s) pass.
|
||||
|
||||
--glob [file] Specify which test to use.
|
||||
|
||||
--fix Fixes tests.
|
||||
|
||||
--bench Benchmarks the test(s).
|
||||
|
||||
--time Times The test(s).
|
||||
|
||||
--minified Runs test file(s) as minified.
|
||||
|
||||
--stop Stop process if a test fails.
|
||||
|
||||
-t, --tokens
|
||||
Output a token stream instead of html.
|
||||
|
||||
--pedantic
|
||||
Conform to obscure parts of markdown.pl as much as possible.
|
||||
Don't fix original markdown bugs.
|
||||
|
||||
--gfm Enable github flavored markdown.
|
||||
|
||||
--breaks
|
||||
Enable GFM line breaks. Only works with the gfm option.
|
||||
|
||||
--sanitize
|
||||
Sanitize output. Ignore any HTML input.
|
||||
|
||||
--smart-lists
|
||||
Use smarter list behavior than the original markdown.
|
||||
|
||||
--lang-prefix [prefix]
|
||||
Set the prefix for code block classes.
|
||||
|
||||
--mangle
|
||||
Mangle email addresses.
|
||||
|
||||
--no-sanitize, -no-etc...
|
||||
The inverse of any of the marked options above.
|
||||
|
||||
--silent
|
||||
Silence error output.
|
||||
|
||||
-h, --help
|
||||
Display help information.
|
||||
|
||||
CONFIGURATION
|
||||
For configuring and running programmatically.
|
||||
|
||||
Example
|
||||
|
||||
require('marked')('*foo*', { gfm: true });
|
||||
|
||||
BUGS
|
||||
Please report any bugs to https://github.com/markedjs/marked.
|
||||
|
||||
LICENSE
|
||||
Copyright (c) 2011-2014, Christopher Jeffrey (MIT License).
|
||||
|
||||
SEE ALSO
|
||||
markdown(1), node.js(1)
|
||||
|
||||
marked(1)
|
||||
3626
packages/markdown/marked/package-lock.json
generated
3626
packages/markdown/marked/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,77 +0,0 @@
|
|||
{
|
||||
"name": "marked",
|
||||
"description": "A markdown parser built for speed",
|
||||
"author": "Christopher Jeffrey",
|
||||
"version": "0.8.0",
|
||||
"main": "./src/marked.js",
|
||||
"bin": "./bin/marked",
|
||||
"man": "./man/marked.1",
|
||||
"files": [
|
||||
"bin/",
|
||||
"lib/",
|
||||
"src/",
|
||||
"man/",
|
||||
"marked.min.js"
|
||||
],
|
||||
"repository": "git://github.com/markedjs/marked.git",
|
||||
"homepage": "https://marked.js.org",
|
||||
"bugs": {
|
||||
"url": "http://github.com/markedjs/marked/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"markup",
|
||||
"html"
|
||||
],
|
||||
"tags": [
|
||||
"markdown",
|
||||
"markup",
|
||||
"html"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@markedjs/html-differ": "^3.0.0",
|
||||
"cheerio": "^0.22.0",
|
||||
"commonmark": "^0.29.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"front-matter": "^3.1.0",
|
||||
"jasmine": "^3.5.0",
|
||||
"markdown": "^0.5.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"rollup": "^2.0.6",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-license": "^0.13.0",
|
||||
"uglify-js": "^3.8.0",
|
||||
"vuln-regex-detector": "^1.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jasmine --config=jasmine.json",
|
||||
"test:all": "npm test && npm run test:lint",
|
||||
"test:unit": "npm test -- test/unit/**/*-spec.js",
|
||||
"test:specs": "npm test -- test/specs/**/*-spec.js",
|
||||
"test:lint": "eslint bin/marked .",
|
||||
"test:redos": "node test/vuln-regex.js",
|
||||
"test:update": "node test/update-specs.js",
|
||||
"bench": "npm run rollup && node test/bench.js",
|
||||
"lint": "eslint --fix bin/marked .",
|
||||
"build:reset": "git checkout upstream/master lib/marked.js lib/marked.esm.js marked.min.js",
|
||||
"build": "npm run rollup && npm run minify",
|
||||
"rollup": "npm run rollup:umd && npm run rollup:esm",
|
||||
"rollup:umd": "rollup -c rollup.config.js",
|
||||
"rollup:esm": "rollup -c rollup.config.esm.js",
|
||||
"minify": "uglifyjs lib/marked.js -cm --comments /Copyright/ -o marked.min.js",
|
||||
"preversion": "npm run build && (git diff --quiet || git commit -am 'build')"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.16.2"
|
||||
}
|
||||
}
|
||||
18
packages/markdown/package.js
Executable file → Normal file
18
packages/markdown/package.js
Executable file → Normal file
|
|
@ -1,24 +1,22 @@
|
|||
// Source: https://github.com/chjj/marked
|
||||
|
||||
Package.describe({
|
||||
name: 'wekan-markdown',
|
||||
summary: "GitHub flavored markdown parser for Meteor based on marked.js",
|
||||
version: "1.0.7",
|
||||
git: "https://github.com/wekan/markdown.git"
|
||||
summary: 'GitHub flavored markdown parser for Meteor based on markdown-it',
|
||||
version: '1.0.9',
|
||||
git: 'https://github.com/wekan/markdown.git',
|
||||
});
|
||||
|
||||
// Before Meteor 0.9?
|
||||
if(!Package.onUse) Package.onUse = Package.on_use;
|
||||
|
||||
Package.onUse(function (api) {
|
||||
if(api.versionsFrom) api.versionsFrom('METEOR@0.9.0');
|
||||
if(api.versionsFrom) api.versionsFrom('1.8.2');
|
||||
|
||||
api.use('templating');
|
||||
api.use("ecmascript", ['server', 'client']);
|
||||
|
||||
api.add_files('marked/lib/marked.js', ['server', 'client']);
|
||||
api.add_files('markdown.js', ['server', 'client']);
|
||||
api.export('Markdown', ['server', 'client']);
|
||||
|
||||
api.use("ui", "client", {weak: true});
|
||||
api.add_files("template-integration.js", "client");
|
||||
api.use('ui', 'client', {weak: true});
|
||||
|
||||
api.add_files('src/template-integration.js', 'client');
|
||||
});
|
||||
|
|
|
|||
0
packages/markdown/smart.json
Executable file → Normal file
0
packages/markdown/smart.json
Executable file → Normal file
78
packages/markdown/src/template-integration.js
Executable file
78
packages/markdown/src/template-integration.js
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
import DOMPurify from 'dompurify';
|
||||
|
||||
var Markdown = require('markdown-it')({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
});
|
||||
|
||||
import markdownItMermaid from "@liradb2000/markdown-it-mermaid";
|
||||
|
||||
/*
|
||||
// Static URL Scheme Listing
|
||||
var urlschemes = [
|
||||
"aodroplink",
|
||||
"thunderlink",
|
||||
"cbthunderlink",
|
||||
"onenote",
|
||||
"file",
|
||||
"abasurl",
|
||||
"conisio",
|
||||
"mailspring"
|
||||
];
|
||||
|
||||
// Better would be a field in the admin backend to set this dynamically
|
||||
// instead of putting all known or wanted url schemes here hard into code
|
||||
// but i was not able to access those settings
|
||||
// var urlschemes = currentSetting.automaticLinkedUrlSchemes.split('\n');
|
||||
|
||||
// put all url schemes into the linkify configuration to automatically make it clickable
|
||||
for(var i=0; i<urlschemes.length;i++){
|
||||
//console.log("adding autolink for "+urlschemes[i]);
|
||||
Markdown.linkify.add(urlschemes[i]+":",'http:');
|
||||
}
|
||||
|
||||
// Additional safeAttrValue function to allow for other specific protocols
|
||||
// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114
|
||||
function mySafeAttrValue(tag, name, value, cssFilter) {
|
||||
// only when the tag is 'a' and attribute is 'href'
|
||||
// then use your custom function
|
||||
if (tag === 'a' && name === 'href') {
|
||||
// only filter the value if starts with an registered url scheme
|
||||
urlscheme = value.split(/:/);
|
||||
//console.log("validating "+urlscheme[0]);
|
||||
if(urlschemes.includes(urlscheme[0])) return value;
|
||||
else {
|
||||
// use the default safeAttrValue function to process all non cbthunderlinks
|
||||
return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
|
||||
}
|
||||
// } else if (tag === 'svg') {
|
||||
// return `<img src="data:image/svg+xml;base64,` + atob(value) + `"></img>`;
|
||||
} else {
|
||||
// use the default safeAttrValue function to process it
|
||||
return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
var emoji = require('markdown-it-emoji');
|
||||
Markdown.use(emoji);
|
||||
Markdown.use(markdownItMermaid);
|
||||
|
||||
if (Package.ui) {
|
||||
const Template = Package.templating.Template;
|
||||
const UI = Package.ui.UI;
|
||||
const HTML = Package.htmljs.HTML;
|
||||
const Blaze = Package.blaze.Blaze; // implied by `ui`
|
||||
|
||||
UI.registerHelper('markdown', new Template('markdown', function () {
|
||||
const self = this;
|
||||
let text = '';
|
||||
if (self.templateContentBlock) {
|
||||
text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING);
|
||||
}
|
||||
|
||||
return HTML.Raw(DOMPurify.sanitize(Markdown.render(text), {ALLOW_UNKNOWN_PROTOCOLS: true}));
|
||||
}));
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
if (Package.ui) {
|
||||
var Template = Package.templating.Template;
|
||||
var UI = Package.ui.UI;
|
||||
var HTML = Package.htmljs.HTML;
|
||||
var Blaze = Package.blaze.Blaze; // implied by `ui`
|
||||
|
||||
UI.registerHelper('markdown', new Template('markdown', function () {
|
||||
var self = this;
|
||||
var text = "";
|
||||
if(self.templateContentBlock) {
|
||||
text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING);
|
||||
}
|
||||
|
||||
return HTML.Raw(Markdown(text));
|
||||
}));
|
||||
}
|
||||
|
|
@ -229,13 +229,13 @@ const casValidate = (req, ticket, token, service, callback) => {
|
|||
if (attrs.debug) {
|
||||
console.log(`CAS response : ${JSON.stringify(result)}`);
|
||||
}
|
||||
let user = Meteor.users.findOne({ 'username': options.username });
|
||||
let user = Users.findOne({ 'username': options.username });
|
||||
if (! user) {
|
||||
if (attrs.debug) {
|
||||
console.log(`Creating user account ${JSON.stringify(options)}`);
|
||||
}
|
||||
const userId = Accounts.insertUserDoc({}, options);
|
||||
user = Meteor.users.findOne(userId);
|
||||
user = Users.findOne(userId);
|
||||
}
|
||||
if (attrs.debug) {
|
||||
console.log(`Using user account ${JSON.stringify(user)}`);
|
||||
|
|
|
|||
25
packages/wekan-accounts-lockout/CONTRIBUTING.md
Normal file
25
packages/wekan-accounts-lockout/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Contributing guide
|
||||
|
||||
Want to contribute to Accounts-Lockout? Awesome!
|
||||
There are many ways you can contribute, see below.
|
||||
|
||||
## Opening issues
|
||||
|
||||
Open an issue to report bugs or to propose new features.
|
||||
|
||||
- Reporting bugs: describe the bug as clearly as you can, including steps to reproduce, what happened and what you were expecting to happen. Also include browser version, OS and other related software's (npm, Node.js, etc) versions when applicable.
|
||||
|
||||
- Proposing features: explain the proposed feature, what it should do, why it is useful, how users should use it. Give us as much info as possible so it will be easier to discuss, access and implement the proposed feature. When you're unsure about a certain aspect of the feature, feel free to leave it open for others to discuss and find an appropriate solution.
|
||||
|
||||
## Proposing pull requests
|
||||
|
||||
Pull requests are very welcome. Note that if you are going to propose drastic changes, be sure to open an issue for discussion first, to make sure that your PR will be accepted before you spend effort coding it.
|
||||
|
||||
Fork the Accounts-Lockout repository, clone it locally and create a branch for your proposed bug fix or new feature. Avoid working directly on the master branch.
|
||||
|
||||
Implement your bug fix or feature, write tests to cover it and make sure all tests are passing (run a final `npm test` to make sure everything is correct). Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)'s master branch.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is extremely important and takes a fair deal of time and effort to write and keep updated.
|
||||
Please submit any and all improvements you can make to the repository's docs.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2019 The Wekan Team
|
||||
Copyright (c) 2017 Lucas Antoniassi de Paiva
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
126
packages/wekan-accounts-lockout/README.md
Normal file
126
packages/wekan-accounts-lockout/README.md
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# Meteor - Accounts - Lockout
|
||||
|
||||
[](https://travis-ci.org/LucasAntoniassi/meteor-accounts-lockout)
|
||||
[](https://www.codacy.com/app/lucasantoniassi/meteor-accounts-lockout?utm_source=github.com&utm_medium=referral&utm_content=LucasAntoniassi/meteor-accounts-lockout&utm_campaign=Badge_Grade)
|
||||
[](https://codeclimate.com/github/LucasAntoniassi/meteor-accounts-lockout)
|
||||
|
||||
## What it is
|
||||
|
||||
Seamless Meteor apps accounts protection from password brute-force attacks.
|
||||
Users won't notice it. Hackers shall not pass.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
meteor add lucasantoniassi:accounts-lockout
|
||||
```
|
||||
|
||||
## Usage via ES6 import
|
||||
|
||||
```javascript
|
||||
// server
|
||||
import { AccountsLockout } from 'meteor/lucasantoniassi:accounts-lockout';
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Default settings:
|
||||
|
||||
```javascript
|
||||
"knownUsers": {
|
||||
"failuresBeforeLockout": 3, // positive integer greater than 0
|
||||
"lockoutPeriod": 60, // in seconds
|
||||
"failureWindow": 10 // in seconds
|
||||
},
|
||||
"unknownUsers": {
|
||||
"failuresBeforeLockout": 3, // positive integer greater than 0
|
||||
"lockoutPeriod": 60, // in seconds
|
||||
"failureWindow": 10 // in seconds
|
||||
}
|
||||
```
|
||||
|
||||
`knownUsers` are users where already belongs to your `Meteor.users` collections,
|
||||
these rules are applied if they attempt to login with an incorrect password but a know email.
|
||||
|
||||
`unknownUsers` are users where **not** belongs to your `Meteor.users` collections,
|
||||
these rules are applied if they attempt to login with a unknown email.
|
||||
|
||||
`failuresBeforeLockout` should be a positive integer greater than 0.
|
||||
|
||||
`lockoutPeriod` should be in seconds.
|
||||
|
||||
`failureWindow` should be in seconds.
|
||||
|
||||
If the `default` is nice to you, you can do that.
|
||||
|
||||
```javascript
|
||||
(new AccountsLockout()).startup();
|
||||
```
|
||||
|
||||
You can overwrite passing an `object` as argument.
|
||||
|
||||
```javascript
|
||||
(new AccountsLockout({
|
||||
knownUsers: {
|
||||
failuresBeforeLockout: 3,
|
||||
lockoutPeriod: 60,
|
||||
failureWindow: 15,
|
||||
},
|
||||
unknownUsers: {
|
||||
failuresBeforeLockout: 3,
|
||||
lockoutPeriod: 60,
|
||||
failureWindow: 15,
|
||||
},
|
||||
})).startup();
|
||||
```
|
||||
|
||||
If you prefer, you can pass a `function` as argument.
|
||||
|
||||
```javascript
|
||||
const knownUsersRules = (user) => {
|
||||
// apply some logic with this user
|
||||
return {
|
||||
failuresBeforeLockout,
|
||||
lockoutPeriod,
|
||||
failureWindow,
|
||||
};
|
||||
};
|
||||
|
||||
const unknownUsersRules = (connection) => {
|
||||
// apply some logic with this connection
|
||||
return {
|
||||
failuresBeforeLockout,
|
||||
lockoutPeriod,
|
||||
failureWindow,
|
||||
};
|
||||
};
|
||||
|
||||
(new AccountsLockout({
|
||||
knownUsers: knownUsersRules,
|
||||
unknownUsers: unknownUsersRules,
|
||||
})).startup();
|
||||
```
|
||||
|
||||
If you prefer, you can use `Meteor.settings`. It will overwrite any previous case.
|
||||
|
||||
```javascript
|
||||
"accounts-lockout": {
|
||||
"knownUsers": {
|
||||
"failuresBeforeLockout": 3,
|
||||
"lockoutPeriod": 60,
|
||||
"failureWindow": 10
|
||||
},
|
||||
"unknownUsers": {
|
||||
"failuresBeforeLockout": 3,
|
||||
"lockoutPeriod": 60,
|
||||
"failureWindow": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
||||
|
||||
5
packages/wekan-accounts-lockout/accounts-lockout.js
Normal file
5
packages/wekan-accounts-lockout/accounts-lockout.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import AccountsLockout from './src/accountsLockout';
|
||||
|
||||
const Name = 'wekan-accounts-lockout';
|
||||
|
||||
export { Name, AccountsLockout };
|
||||
18
packages/wekan-accounts-lockout/package.js
Normal file
18
packages/wekan-accounts-lockout/package.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* global Package */
|
||||
|
||||
Package.describe({
|
||||
name: 'wekan-accounts-lockout',
|
||||
version: '1.0.0',
|
||||
summary: 'Meteor package for locking user accounts and stopping brute force attacks',
|
||||
git: 'https://github.com/lucasantoniassi/meteor-accounts-lockout.git',
|
||||
documentation: 'README.md',
|
||||
});
|
||||
|
||||
Package.onUse((api) => {
|
||||
api.versionsFrom('1.4.2.3');
|
||||
api.use([
|
||||
'ecmascript',
|
||||
'accounts-password',
|
||||
]);
|
||||
api.mainModule('accounts-lockout.js');
|
||||
});
|
||||
4
packages/wekan-accounts-lockout/package.json
Normal file
4
packages/wekan-accounts-lockout/package.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "wekan-accounts-lockout",
|
||||
"private": true
|
||||
}
|
||||
29
packages/wekan-accounts-lockout/src/accountsLockout.js
Normal file
29
packages/wekan-accounts-lockout/src/accountsLockout.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import KnownUser from './knownUser';
|
||||
import UnknownUser from './unknownUser';
|
||||
|
||||
class AccountsLockout {
|
||||
constructor({
|
||||
knownUsers = {
|
||||
failuresBeforeLockout: 3,
|
||||
lockoutPeriod: 60,
|
||||
failureWindow: 15,
|
||||
},
|
||||
unknownUsers = {
|
||||
failuresBeforeLockout: 3,
|
||||
lockoutPeriod: 60,
|
||||
failureWindow: 15,
|
||||
},
|
||||
}) {
|
||||
this.settings = {
|
||||
knownUsers,
|
||||
unknownUsers,
|
||||
};
|
||||
}
|
||||
|
||||
startup() {
|
||||
(new KnownUser(this.settings.knownUsers)).startup();
|
||||
(new UnknownUser(this.settings.unknownUsers)).startup();
|
||||
}
|
||||
}
|
||||
|
||||
export default AccountsLockout;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export default new Meteor.Collection('AccountsLockout.Connections');
|
||||
321
packages/wekan-accounts-lockout/src/knownUser.js
Normal file
321
packages/wekan-accounts-lockout/src/knownUser.js
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
class KnownUser {
|
||||
constructor(settings) {
|
||||
this.unchangedSettings = settings;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
startup() {
|
||||
if (!(this.unchangedSettings instanceof Function)) {
|
||||
this.updateSettings();
|
||||
}
|
||||
this.scheduleUnlocksForLockedAccounts();
|
||||
KnownUser.unlockAccountsIfLockoutAlreadyExpired();
|
||||
this.hookIntoAccounts();
|
||||
}
|
||||
|
||||
updateSettings() {
|
||||
const settings = KnownUser.knownUsers();
|
||||
if (settings) {
|
||||
settings.forEach(function updateSetting({ key, value }) {
|
||||
this.settings[key] = value;
|
||||
});
|
||||
}
|
||||
this.validateSettings();
|
||||
}
|
||||
|
||||
validateSettings() {
|
||||
if (
|
||||
!this.settings.failuresBeforeLockout ||
|
||||
this.settings.failuresBeforeLockout < 0
|
||||
) {
|
||||
throw new Error('"failuresBeforeLockout" is not positive integer');
|
||||
}
|
||||
if (
|
||||
!this.settings.lockoutPeriod ||
|
||||
this.settings.lockoutPeriod < 0
|
||||
) {
|
||||
throw new Error('"lockoutPeriod" is not positive integer');
|
||||
}
|
||||
if (
|
||||
!this.settings.failureWindow ||
|
||||
this.settings.failureWindow < 0
|
||||
) {
|
||||
throw new Error('"failureWindow" is not positive integer');
|
||||
}
|
||||
}
|
||||
|
||||
scheduleUnlocksForLockedAccounts() {
|
||||
const lockedAccountsCursor = Meteor.users.find(
|
||||
{
|
||||
'services.accounts-lockout.unlockTime': {
|
||||
$gt: Number(new Date()),
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
'services.accounts-lockout.unlockTime': 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
const currentTime = Number(new Date());
|
||||
lockedAccountsCursor.forEach((user) => {
|
||||
let lockDuration = KnownUser.unlockTime(user) - currentTime;
|
||||
if (lockDuration >= this.settings.lockoutPeriod) {
|
||||
lockDuration = this.settings.lockoutPeriod * 1000;
|
||||
}
|
||||
if (lockDuration <= 1) {
|
||||
lockDuration = 1;
|
||||
}
|
||||
Meteor.setTimeout(
|
||||
KnownUser.unlockAccount.bind(null, user._id),
|
||||
lockDuration,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static unlockAccountsIfLockoutAlreadyExpired() {
|
||||
const currentTime = Number(new Date());
|
||||
const query = {
|
||||
'services.accounts-lockout.unlockTime': {
|
||||
$lt: currentTime,
|
||||
},
|
||||
};
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
}
|
||||
|
||||
hookIntoAccounts() {
|
||||
Accounts.validateLoginAttempt(this.validateLoginAttempt.bind(this));
|
||||
Accounts.onLogin(KnownUser.onLogin);
|
||||
}
|
||||
|
||||
|
||||
validateLoginAttempt(loginInfo) {
|
||||
if (
|
||||
// don't interrupt non-password logins
|
||||
loginInfo.type !== 'password' ||
|
||||
loginInfo.user === undefined ||
|
||||
// Don't handle errors unless they are due to incorrect password
|
||||
(loginInfo.error !== undefined && loginInfo.error.reason !== 'Incorrect password')
|
||||
) {
|
||||
return loginInfo.allowed;
|
||||
}
|
||||
|
||||
// If there was no login error and the account is NOT locked, don't interrupt
|
||||
const unlockTime = KnownUser.unlockTime(loginInfo.user);
|
||||
if (loginInfo.error === undefined && unlockTime === 0) {
|
||||
return loginInfo.allowed;
|
||||
}
|
||||
|
||||
if (this.unchangedSettings instanceof Function) {
|
||||
this.settings = this.unchangedSettings(loginInfo.user);
|
||||
this.validateSettings();
|
||||
}
|
||||
|
||||
const userId = loginInfo.user._id;
|
||||
let failedAttempts = 1 + KnownUser.failedAttempts(loginInfo.user);
|
||||
const firstFailedAttempt = KnownUser.firstFailedAttempt(loginInfo.user);
|
||||
const currentTime = Number(new Date());
|
||||
|
||||
const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow);
|
||||
if (canReset) {
|
||||
failedAttempts = 1;
|
||||
KnownUser.resetAttempts(failedAttempts, userId);
|
||||
}
|
||||
|
||||
const canIncrement = failedAttempts < this.settings.failuresBeforeLockout;
|
||||
if (canIncrement) {
|
||||
KnownUser.incrementAttempts(failedAttempts, userId);
|
||||
}
|
||||
|
||||
const maxAttemptsAllowed = this.settings.failuresBeforeLockout;
|
||||
const attemptsRemaining = maxAttemptsAllowed - failedAttempts;
|
||||
if (unlockTime > currentTime) {
|
||||
let duration = unlockTime - currentTime;
|
||||
duration = Math.ceil(duration / 1000);
|
||||
duration = duration > 1 ? duration : 1;
|
||||
KnownUser.tooManyAttempts(duration);
|
||||
}
|
||||
if (failedAttempts === maxAttemptsAllowed) {
|
||||
this.setNewUnlockTime(failedAttempts, userId);
|
||||
|
||||
let duration = this.settings.lockoutPeriod;
|
||||
duration = Math.ceil(duration);
|
||||
duration = duration > 1 ? duration : 1;
|
||||
return KnownUser.tooManyAttempts(duration);
|
||||
}
|
||||
return KnownUser.incorrectPassword(
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
);
|
||||
}
|
||||
|
||||
static resetAttempts(
|
||||
failedAttempts,
|
||||
userId,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const query = { _id: userId };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
'services.accounts-lockout.firstFailedAttempt': currentTime,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
}
|
||||
|
||||
static incrementAttempts(
|
||||
failedAttempts,
|
||||
userId,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const query = { _id: userId };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
}
|
||||
|
||||
setNewUnlockTime(
|
||||
failedAttempts,
|
||||
userId,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const newUnlockTime = (1000 * this.settings.lockoutPeriod) + currentTime;
|
||||
const query = { _id: userId };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
'services.accounts-lockout.unlockTime': newUnlockTime,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
Meteor.setTimeout(
|
||||
KnownUser.unlockAccount.bind(null, userId),
|
||||
this.settings.lockoutPeriod * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
static onLogin(loginInfo) {
|
||||
if (loginInfo.type !== 'password') {
|
||||
return;
|
||||
}
|
||||
const userId = loginInfo.user._id;
|
||||
const query = { _id: userId };
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
}
|
||||
|
||||
static incorrectPassword(
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
) {
|
||||
throw new Meteor.Error(
|
||||
403,
|
||||
'Incorrect password',
|
||||
JSON.stringify({
|
||||
message: 'Incorrect password',
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static tooManyAttempts(duration) {
|
||||
throw new Meteor.Error(
|
||||
403,
|
||||
'Too many attempts',
|
||||
JSON.stringify({
|
||||
message: 'Wrong passwords were submitted too many times. Account is locked for a while.',
|
||||
duration,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static knownUsers() {
|
||||
let knownUsers;
|
||||
try {
|
||||
knownUsers = Meteor.settings['accounts-lockout'].knownUsers;
|
||||
} catch (e) {
|
||||
knownUsers = false;
|
||||
}
|
||||
return knownUsers || false;
|
||||
}
|
||||
|
||||
static unlockTime(user) {
|
||||
let unlockTime;
|
||||
try {
|
||||
unlockTime = user.services['accounts-lockout'].unlockTime;
|
||||
} catch (e) {
|
||||
unlockTime = 0;
|
||||
}
|
||||
return unlockTime || 0;
|
||||
}
|
||||
|
||||
static failedAttempts(user) {
|
||||
let failedAttempts;
|
||||
try {
|
||||
failedAttempts = user.services['accounts-lockout'].failedAttempts;
|
||||
} catch (e) {
|
||||
failedAttempts = 0;
|
||||
}
|
||||
return failedAttempts || 0;
|
||||
}
|
||||
|
||||
static lastFailedAttempt(user) {
|
||||
let lastFailedAttempt;
|
||||
try {
|
||||
lastFailedAttempt = user.services['accounts-lockout'].lastFailedAttempt;
|
||||
} catch (e) {
|
||||
lastFailedAttempt = 0;
|
||||
}
|
||||
return lastFailedAttempt || 0;
|
||||
}
|
||||
|
||||
static firstFailedAttempt(user) {
|
||||
let firstFailedAttempt;
|
||||
try {
|
||||
firstFailedAttempt = user.services['accounts-lockout'].firstFailedAttempt;
|
||||
} catch (e) {
|
||||
firstFailedAttempt = 0;
|
||||
}
|
||||
return firstFailedAttempt || 0;
|
||||
}
|
||||
|
||||
static unlockAccount(userId) {
|
||||
const query = { _id: userId };
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
Meteor.users.update(query, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default KnownUser;
|
||||
329
packages/wekan-accounts-lockout/src/unknownUser.js
Normal file
329
packages/wekan-accounts-lockout/src/unknownUser.js
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import _AccountsLockoutCollection from './accountsLockoutCollection';
|
||||
|
||||
class UnknownUser {
|
||||
constructor(
|
||||
settings,
|
||||
{
|
||||
AccountsLockoutCollection = _AccountsLockoutCollection,
|
||||
} = {},
|
||||
) {
|
||||
this.AccountsLockoutCollection = AccountsLockoutCollection;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
startup() {
|
||||
if (!(this.settings instanceof Function)) {
|
||||
this.updateSettings();
|
||||
}
|
||||
this.scheduleUnlocksForLockedAccounts();
|
||||
this.unlockAccountsIfLockoutAlreadyExpired();
|
||||
this.hookIntoAccounts();
|
||||
}
|
||||
|
||||
updateSettings() {
|
||||
const settings = UnknownUser.unknownUsers();
|
||||
if (settings) {
|
||||
settings.forEach(function updateSetting({ key, value }) {
|
||||
this.settings[key] = value;
|
||||
});
|
||||
}
|
||||
this.validateSettings();
|
||||
}
|
||||
|
||||
validateSettings() {
|
||||
if (
|
||||
!this.settings.failuresBeforeLockout ||
|
||||
this.settings.failuresBeforeLockout < 0
|
||||
) {
|
||||
throw new Error('"failuresBeforeLockout" is not positive integer');
|
||||
}
|
||||
if (
|
||||
!this.settings.lockoutPeriod ||
|
||||
this.settings.lockoutPeriod < 0
|
||||
) {
|
||||
throw new Error('"lockoutPeriod" is not positive integer');
|
||||
}
|
||||
if (
|
||||
!this.settings.failureWindow ||
|
||||
this.settings.failureWindow < 0
|
||||
) {
|
||||
throw new Error('"failureWindow" is not positive integer');
|
||||
}
|
||||
}
|
||||
|
||||
scheduleUnlocksForLockedAccounts() {
|
||||
const lockedAccountsCursor = this.AccountsLockoutCollection.find(
|
||||
{
|
||||
'services.accounts-lockout.unlockTime': {
|
||||
$gt: Number(new Date()),
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
'services.accounts-lockout.unlockTime': 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
const currentTime = Number(new Date());
|
||||
lockedAccountsCursor.forEach((connection) => {
|
||||
let lockDuration = this.unlockTime(connection) - currentTime;
|
||||
if (lockDuration >= this.settings.lockoutPeriod) {
|
||||
lockDuration = this.settings.lockoutPeriod * 1000;
|
||||
}
|
||||
if (lockDuration <= 1) {
|
||||
lockDuration = 1;
|
||||
}
|
||||
Meteor.setTimeout(
|
||||
this.unlockAccount.bind(this, connection.clientAddress),
|
||||
lockDuration,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
unlockAccountsIfLockoutAlreadyExpired() {
|
||||
const currentTime = Number(new Date());
|
||||
const query = {
|
||||
'services.accounts-lockout.unlockTime': {
|
||||
$lt: currentTime,
|
||||
},
|
||||
};
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.update(query, data);
|
||||
}
|
||||
|
||||
hookIntoAccounts() {
|
||||
Accounts.validateLoginAttempt(this.validateLoginAttempt.bind(this));
|
||||
Accounts.onLogin(this.onLogin.bind(this));
|
||||
}
|
||||
|
||||
validateLoginAttempt(loginInfo) {
|
||||
// don't interrupt non-password logins
|
||||
if (
|
||||
loginInfo.type !== 'password' ||
|
||||
loginInfo.user !== undefined ||
|
||||
loginInfo.error === undefined ||
|
||||
loginInfo.error.reason !== 'User not found'
|
||||
) {
|
||||
return loginInfo.allowed;
|
||||
}
|
||||
|
||||
if (this.settings instanceof Function) {
|
||||
this.settings = this.settings(loginInfo.connection);
|
||||
this.validateSettings();
|
||||
}
|
||||
|
||||
const clientAddress = loginInfo.connection.clientAddress;
|
||||
const unlockTime = this.unlockTime(loginInfo.connection);
|
||||
let failedAttempts = 1 + this.failedAttempts(loginInfo.connection);
|
||||
const firstFailedAttempt = this.firstFailedAttempt(loginInfo.connection);
|
||||
const currentTime = Number(new Date());
|
||||
|
||||
const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow);
|
||||
if (canReset) {
|
||||
failedAttempts = 1;
|
||||
this.resetAttempts(failedAttempts, clientAddress);
|
||||
}
|
||||
|
||||
const canIncrement = failedAttempts < this.settings.failuresBeforeLockout;
|
||||
if (canIncrement) {
|
||||
this.incrementAttempts(failedAttempts, clientAddress);
|
||||
}
|
||||
|
||||
const maxAttemptsAllowed = this.settings.failuresBeforeLockout;
|
||||
const attemptsRemaining = maxAttemptsAllowed - failedAttempts;
|
||||
if (unlockTime > currentTime) {
|
||||
let duration = unlockTime - currentTime;
|
||||
duration = Math.ceil(duration / 1000);
|
||||
duration = duration > 1 ? duration : 1;
|
||||
UnknownUser.tooManyAttempts(duration);
|
||||
}
|
||||
if (failedAttempts === maxAttemptsAllowed) {
|
||||
this.setNewUnlockTime(failedAttempts, clientAddress);
|
||||
|
||||
let duration = this.settings.lockoutPeriod;
|
||||
duration = Math.ceil(duration);
|
||||
duration = duration > 1 ? duration : 1;
|
||||
return UnknownUser.tooManyAttempts(duration);
|
||||
}
|
||||
return UnknownUser.userNotFound(
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
);
|
||||
}
|
||||
|
||||
resetAttempts(
|
||||
failedAttempts,
|
||||
clientAddress,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const query = { clientAddress };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
'services.accounts-lockout.firstFailedAttempt': currentTime,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.upsert(query, data);
|
||||
}
|
||||
|
||||
incrementAttempts(
|
||||
failedAttempts,
|
||||
clientAddress,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const query = { clientAddress };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.upsert(query, data);
|
||||
}
|
||||
|
||||
setNewUnlockTime(
|
||||
failedAttempts,
|
||||
clientAddress,
|
||||
) {
|
||||
const currentTime = Number(new Date());
|
||||
const newUnlockTime = (1000 * this.settings.lockoutPeriod) + currentTime;
|
||||
const query = { clientAddress };
|
||||
const data = {
|
||||
$set: {
|
||||
'services.accounts-lockout.failedAttempts': failedAttempts,
|
||||
'services.accounts-lockout.lastFailedAttempt': currentTime,
|
||||
'services.accounts-lockout.unlockTime': newUnlockTime,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.upsert(query, data);
|
||||
Meteor.setTimeout(
|
||||
this.unlockAccount.bind(this, clientAddress),
|
||||
this.settings.lockoutPeriod * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
onLogin(loginInfo) {
|
||||
if (loginInfo.type !== 'password') {
|
||||
return;
|
||||
}
|
||||
const clientAddress = loginInfo.connection.clientAddress;
|
||||
const query = { clientAddress };
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.update(query, data);
|
||||
}
|
||||
|
||||
static userNotFound(
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
) {
|
||||
throw new Meteor.Error(
|
||||
403,
|
||||
'User not found',
|
||||
JSON.stringify({
|
||||
message: 'User not found',
|
||||
failedAttempts,
|
||||
maxAttemptsAllowed,
|
||||
attemptsRemaining,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static tooManyAttempts(duration) {
|
||||
throw new Meteor.Error(
|
||||
403,
|
||||
'Too many attempts',
|
||||
JSON.stringify({
|
||||
message: 'Wrong emails were submitted too many times. Account is locked for a while.',
|
||||
duration,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static unknownUsers() {
|
||||
let unknownUsers;
|
||||
try {
|
||||
unknownUsers = Meteor.settings['accounts-lockout'].unknownUsers;
|
||||
} catch (e) {
|
||||
unknownUsers = false;
|
||||
}
|
||||
return unknownUsers || false;
|
||||
}
|
||||
|
||||
findOneByConnection(connection) {
|
||||
return this.AccountsLockoutCollection.findOne({
|
||||
clientAddress: connection.clientAddress,
|
||||
});
|
||||
}
|
||||
|
||||
unlockTime(connection) {
|
||||
connection = this.findOneByConnection(connection);
|
||||
let unlockTime;
|
||||
try {
|
||||
unlockTime = connection.services['accounts-lockout'].unlockTime;
|
||||
} catch (e) {
|
||||
unlockTime = 0;
|
||||
}
|
||||
return unlockTime || 0;
|
||||
}
|
||||
|
||||
failedAttempts(connection) {
|
||||
connection = this.findOneByConnection(connection);
|
||||
let failedAttempts;
|
||||
try {
|
||||
failedAttempts = connection.services['accounts-lockout'].failedAttempts;
|
||||
} catch (e) {
|
||||
failedAttempts = 0;
|
||||
}
|
||||
return failedAttempts || 0;
|
||||
}
|
||||
|
||||
lastFailedAttempt(connection) {
|
||||
connection = this.findOneByConnection(connection);
|
||||
let lastFailedAttempt;
|
||||
try {
|
||||
lastFailedAttempt = connection.services['accounts-lockout'].lastFailedAttempt;
|
||||
} catch (e) {
|
||||
lastFailedAttempt = 0;
|
||||
}
|
||||
return lastFailedAttempt || 0;
|
||||
}
|
||||
|
||||
firstFailedAttempt(connection) {
|
||||
connection = this.findOneByConnection(connection);
|
||||
let firstFailedAttempt;
|
||||
try {
|
||||
firstFailedAttempt = connection.services['accounts-lockout'].firstFailedAttempt;
|
||||
} catch (e) {
|
||||
firstFailedAttempt = 0;
|
||||
}
|
||||
return firstFailedAttempt || 0;
|
||||
}
|
||||
|
||||
unlockAccount(clientAddress) {
|
||||
const query = { clientAddress };
|
||||
const data = {
|
||||
$unset: {
|
||||
'services.accounts-lockout.unlockTime': 0,
|
||||
'services.accounts-lockout.failedAttempts': 0,
|
||||
},
|
||||
};
|
||||
this.AccountsLockoutCollection.update(query, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default UnknownUser;
|
||||
5
packages/wekan-cfs-access-point/.travis.yml
Normal file
5
packages/wekan-cfs-access-point/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# Changelog
|
||||
|
||||
## [v0.1.50] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 21/1/19 by Harry Adel
|
||||
|
||||
- Bump to version 0.1.50
|
||||
|
||||
- *Merged pull-request:* "filename conversion for FS.HTTP.Handlers.Get" [#9](https://github.com/zcfs/Meteor-CollectionFS/pull/994) ([yatusiter](https://github.com/yatusiter))
|
||||
|
||||
|
||||
|
||||
## [v0.1.46] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 30/3/15 by Eric Dobbertin
|
||||
|
||||
- Bump to version 0.1.46
|
||||
|
||||
- *Merged pull-request:* [#611](https://github.com/zcfs/Meteor-CollectionFS/issues/611)
|
||||
|
||||
- Exposed request handlers on `FS.HTTP.Handlers` object so that app can override
|
||||
|
||||
## [v0.1.43] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.43)
|
||||
#### 20/12/14 by Morten Henriksen
|
||||
- add changelog
|
||||
|
||||
- Bump to version 0.1.43
|
||||
|
||||
- *Fixed bug:* "Doesn't work in IE 8" [#10](https://github.com/zcfs/Meteor-cfs-access-point/issues/10)
|
||||
|
||||
- *Merged pull-request:* "rootUrlPathPrefix fix for cordova" [#9](https://github.com/zcfs/Meteor-cfs-access-point/issues/9) ([dmitriyles](https://github.com/dmitriyles))
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
Patches by GitHub users [@dmitriyles](https://github.com/dmitriyles), [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.1.42] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.42)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
## [v0.1.41] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.41)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Cordova rootUrlPathPrefix fix
|
||||
|
||||
- Bump to version 0.1.41
|
||||
|
||||
## [v0.1.40] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.40)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr fixed warnings
|
||||
|
||||
- fixes to GET handler
|
||||
|
||||
- add back tests
|
||||
|
||||
- support apps in server subdirectories; closes #8
|
||||
|
||||
- 0.9.1 support
|
||||
|
||||
## [v0.0.39] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.39)
|
||||
#### 28/08/14 by Morten Henriksen
|
||||
- Meteor Package System Update
|
||||
|
||||
## [v0.0.38] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.38)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
## [v0.0.37] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.37)
|
||||
#### 26/08/14 by Eric Dobbertin
|
||||
- change package name to lowercase
|
||||
|
||||
## [v0.0.36] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.36)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- pass correct arg
|
||||
|
||||
## [v0.0.35] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.35)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- move to correct place
|
||||
|
||||
## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.34)
|
||||
#### 05/08/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Added contentLength for ranges and inline content" [#5](https://github.com/zcfs/Meteor-cfs-access-point/issues/5) ([maomorales](https://github.com/maomorales))
|
||||
|
||||
- Content-Length and Last-Modified headers
|
||||
|
||||
Patches by GitHub user [@maomorales](https://github.com/maomorales).
|
||||
|
||||
## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.33)
|
||||
#### 31/07/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Force browser to download with filename passed in url" [#3](https://github.com/zcfs/Meteor-cfs-access-point/issues/3) ([elbowz](https://github.com/elbowz))
|
||||
|
||||
- Force browser to download with filename passed in url
|
||||
|
||||
Patches by GitHub user [@elbowz](https://github.com/elbowz).
|
||||
|
||||
## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.32)
|
||||
#### 28/07/14 by Eric Dobbertin
|
||||
- support collection-specific GET headers
|
||||
|
||||
- update API docs
|
||||
|
||||
## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.31)
|
||||
#### 06/07/14 by Eric Dobbertin
|
||||
- allow override filename
|
||||
|
||||
## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.30)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- ignore auth on server so that url method can be called on the server
|
||||
|
||||
## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.29)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- rework the new authtoken stuff to make it easier to debug and cleaner
|
||||
|
||||
## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.28)
|
||||
#### 29/04/14 by Eric Dobbertin
|
||||
- generate api docs
|
||||
|
||||
- adjustments to use new FS.File API functions, plus have `url` function omit query string whenever possible
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
- Switched to HTTP.call() to get the server time
|
||||
|
||||
- Better check for options.auth being a number. Check to see if we have Buffer() available on the server side. New check to make sure we have the token. Switched Metheor.method to HTTP.methods for the getServerTime() function.
|
||||
|
||||
- Expiration is now optional. If auth is set to a number, that is the number of seconds the token is valid for.
|
||||
|
||||
- Added time sync with the server for token generation.
|
||||
|
||||
- Added code to pass a token with a set expiration date from the client. Added token check on the server side.
|
||||
|
||||
Patches by GitHub user [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.27)
|
||||
#### 08/04/14 by Eric Dobbertin
|
||||
- clean up/fix whole-file upload handler
|
||||
|
||||
## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.26)
|
||||
#### 07/04/14 by Eric Dobbertin
|
||||
- add URL options to get temporary images while uploading and storing
|
||||
|
||||
## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.25)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- * allow `setBaseUrl` to be called either outside of Meteor.startup or inside * move encodeParams helper to FS.Utility
|
||||
|
||||
## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.24)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- properly remount URLs
|
||||
|
||||
- when uploading chunks, check the insert allow/deny since it's part of inserting
|
||||
|
||||
## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.23)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.22)
|
||||
#### 29/03/14 by Morten Henriksen
|
||||
- remove underscore deps
|
||||
|
||||
## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.21)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- add comments about shareId
|
||||
|
||||
## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.20)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- Rollback to specific git dependency
|
||||
|
||||
- Try modified test script
|
||||
|
||||
- deps are already in collectionFS
|
||||
|
||||
## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.19)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- try to fix travis test by using general package references
|
||||
|
||||
## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.18)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- If the read stream fails we send an error to the client
|
||||
|
||||
## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.17)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- remove smart lock
|
||||
|
||||
- commit smart.lock, trying to get tests to pass on travis
|
||||
|
||||
- some minor pkg adjustments; trying to get tests to pass on travis
|
||||
|
||||
## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.16)
|
||||
#### 18/03/14 by Morten Henriksen
|
||||
- Rollback to using the direct storage adapter - makes more sense when serving files
|
||||
|
||||
- shift to new http.methods streaming api
|
||||
|
||||
- move server side DDP access points to cfs-download-ddp pkg; update API docs
|
||||
|
||||
- fix typo...
|
||||
|
||||
- return something useful
|
||||
|
||||
- convert to streaming
|
||||
|
||||
- Add streaming WIP
|
||||
|
||||
- fix/adjust some tests; minor improvements to some handlers
|
||||
|
||||
- Add unmount and allow mount to use default selector function
|
||||
|
||||
- Refactor access point - wip
|
||||
|
||||
## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.15)
|
||||
#### 05/03/14 by Morten Henriksen
|
||||
- Refactor note, encode stuff should be prefixed into FS.Utility
|
||||
|
||||
- FS.File.url add user deps when auth is used
|
||||
|
||||
- fix url method
|
||||
|
||||
- query string fix
|
||||
|
||||
- move PUT access points for HTTP upload into this package; mount DELETE on /record/ as well as /files/; some fixes and improvements to handlers
|
||||
|
||||
## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.14)
|
||||
#### 03/03/14 by Eric Dobbertin
|
||||
- better error; return Buffer instead of converting to Uint8Array
|
||||
|
||||
## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.13)
|
||||
#### 02/03/14 by Eric Dobbertin
|
||||
- more tests, make everything work, add unpublish method
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.12)
|
||||
#### 01/03/14 by Eric Dobbertin
|
||||
- add travis-ci image
|
||||
|
||||
- rework URLs a bit, use http-publish package to publish FS.Collection listing, and add a test for this (!)
|
||||
|
||||
- add http-publish dependency
|
||||
|
||||
- del should be delete
|
||||
|
||||
## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.11)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move some code to other packages; redo the HTTP GET/DEL methods
|
||||
|
||||
## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.10)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move DDP upload methods to new cfs-upload-ddp package
|
||||
|
||||
## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.9)
|
||||
#### 21/02/14 by Eric Dobbertin
|
||||
- new URL syntax; use the store's file key instead of ID; also fix allow/deny checks with insecure
|
||||
|
||||
## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.8)
|
||||
#### 20/02/14 by Eric Dobbertin
|
||||
- support HTTP PUT of new file and fix PUT of existing file
|
||||
|
||||
## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.7)
|
||||
#### 17/02/14 by Morten Henriksen
|
||||
- add http-methods dependency
|
||||
|
||||
## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.6)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.5)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
- a few fixes and improvements
|
||||
|
||||
- need to actually mount it
|
||||
|
||||
- attempt at switching to generic HTTP access point; also add support for chunked http downloads (range header)
|
||||
|
||||
## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.4)
|
||||
#### 15/02/14 by Morten Henriksen
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
- corrected typo
|
||||
|
||||
- added debugging
|
||||
|
||||
- call HTTP.methods on server only
|
||||
|
||||
- run client side, too, for side effects
|
||||
|
||||
- rework for additional abstraction; also DDP methods don't need to be per-collection so they no longer are
|
||||
|
||||
## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.3)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.2)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.1)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
- init commit
|
||||
|
||||
20
packages/wekan-cfs-access-point/LICENSE.md
Normal file
20
packages/wekan-cfs-access-point/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
32
packages/wekan-cfs-access-point/README.md
Normal file
32
packages/wekan-cfs-access-point/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
wekan-cfs-access-point [](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point)
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is added when you
|
||||
add the `wekan-cfs-standard-packages` package. You could potentially use your own access point
|
||||
package instead.
|
||||
|
||||
## Define a URL for Collection Listing
|
||||
|
||||
To define a URL that accepts GET requests and returns a list of published
|
||||
files in a FS.Collection:
|
||||
|
||||
```js
|
||||
Images = new FS.Collection("images", {
|
||||
stores: [myStore]
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
// `this` provides a context similar to Meteor.publish
|
||||
return Images.find();
|
||||
});
|
||||
```
|
||||
|
||||
The URL will be '/cfs/record/images', where the `cfs` piece is configurable
|
||||
using the `FS.HTTP.setBaseUrl` method.
|
||||
|
||||
## API Documentation
|
||||
|
||||
[Here](api.md)
|
||||
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
FS.HTTP.setHeadersForGet = function setHeadersForGet() {
|
||||
// Client Stub
|
||||
};
|
||||
|
||||
FS.HTTP.now = function() {
|
||||
return new Date(new Date() + FS.HTTP._serverTimeDiff);
|
||||
};
|
||||
|
||||
// Returns the localstorage if its found and working
|
||||
// TODO: check if this works in IE
|
||||
// could use Meteor._localStorage - just needs a rewrite
|
||||
FS.HTTP._storage = function() {
|
||||
var storage,
|
||||
fail,
|
||||
uid;
|
||||
try {
|
||||
uid = "test";
|
||||
(storage = window.localStorage).setItem(uid, uid);
|
||||
fail = (storage.getItem(uid) !== uid);
|
||||
storage.removeItem(uid);
|
||||
if (fail) {
|
||||
storage = false;
|
||||
}
|
||||
} catch(e) {
|
||||
console.log("Error initializing storage for FS.HTTP");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return storage;
|
||||
};
|
||||
|
||||
// get our storage if found
|
||||
FS.HTTP.storage = FS.HTTP._storage();
|
||||
|
||||
FS.HTTP._prefix = 'fsHTTP.';
|
||||
|
||||
FS.HTTP._serverTimeDiff = 0; // Time difference in ms
|
||||
|
||||
if (FS.HTTP.storage) {
|
||||
// Initialize the FS.HTTP._serverTimeDiff
|
||||
FS.HTTP._serverTimeDiff = (1*FS.HTTP.storage.getItem(FS.HTTP._prefix+'timeDiff')) || 0;
|
||||
// At client startup we figure out the time difference between server and
|
||||
// client time - this includes lag and timezone
|
||||
Meteor.startup(function() {
|
||||
// Call the server method an get server time
|
||||
HTTP.get(rootUrlPathPrefix + '/cfs/servertime', function(error, result) {
|
||||
if (!error) {
|
||||
// Update our server time diff
|
||||
var dateNew = new Date(+result.content);
|
||||
FS.HTTP._serverTimeDiff = dateNew - new Date();// - lag or/and timezone
|
||||
// Update the localstorage
|
||||
FS.HTTP.storage.setItem(FS.HTTP._prefix + 'timeDiff', FS.HTTP._serverTimeDiff);
|
||||
} else {
|
||||
console.log(error.message);
|
||||
}
|
||||
}); // EO Server call
|
||||
});
|
||||
}
|
||||
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";
|
||||
// Adjust the rootUrlPathPrefix if necessary
|
||||
if (rootUrlPathPrefix.length > 0) {
|
||||
if (rootUrlPathPrefix.slice(0, 1) !== '/') {
|
||||
rootUrlPathPrefix = '/' + rootUrlPathPrefix;
|
||||
}
|
||||
if (rootUrlPathPrefix.slice(-1) === '/') {
|
||||
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// prepend ROOT_URL when isCordova
|
||||
if (Meteor.isCordova) {
|
||||
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
baseUrl = '/cfs';
|
||||
FS.HTTP = FS.HTTP || {};
|
||||
|
||||
// Note the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setBaseUrl
|
||||
* @public
|
||||
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {
|
||||
|
||||
// Adjust the baseUrl if necessary
|
||||
if (newBaseUrl.slice(0, 1) !== '/') {
|
||||
newBaseUrl = '/' + newBaseUrl;
|
||||
}
|
||||
if (newBaseUrl.slice(-1) === '/') {
|
||||
newBaseUrl = newBaseUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
// Update the base URL
|
||||
baseUrl = newBaseUrl;
|
||||
|
||||
// Change the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
// Remount URLs with the new baseUrl, unmounting the old, on the server only.
|
||||
// If existingMountPoints is empty, then we haven't run the server startup
|
||||
// code yet, so this new URL will be used at that point for the initial mount.
|
||||
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {
|
||||
mountUrls();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* FS.File extensions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.urlRelative Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.returnWhenStored=false] Flag relevant only on server, Return the URL only when file has been saved to the requested store.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the relative HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.urlRelative = function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
options = FS.Utility.extend({
|
||||
store: null,
|
||||
auth: null,
|
||||
download: false,
|
||||
metadata: false,
|
||||
brokenIsFine: false,
|
||||
returnWhenStored: false,
|
||||
uploading: null, // return this URL while uploading
|
||||
storing: null, // return this URL while storing
|
||||
filename: null // override the filename that is shown to the user
|
||||
}, options.hash || options); // check for "hash" prop if called as helper
|
||||
|
||||
// Primarily useful for displaying a temporary image while uploading an image
|
||||
if (options.uploading && !self.isUploaded()) {
|
||||
return options.uploading;
|
||||
}
|
||||
|
||||
if (self.isMounted()) {
|
||||
// See if we've stored in the requested store yet
|
||||
var storeName = options.store || self.collection.primaryStore.name;
|
||||
if (!self.hasStored(storeName)) {
|
||||
if (options.storing) {
|
||||
return options.storing;
|
||||
} else if (!options.brokenIsFine) {
|
||||
// In case we want to get back the url only when he is stored
|
||||
if (Meteor.isServer && options.returnWhenStored) {
|
||||
// Wait till file is stored to storeName
|
||||
self.onStored(storeName);
|
||||
} else {
|
||||
// We want to return null if we know the URL will be a broken
|
||||
// link because then we can avoid rendering broken links, broken
|
||||
// images, etc.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add filename to end of URL if we can determine one
|
||||
var filename = options.filename || self.name({store: storeName});
|
||||
if (typeof filename === "string" && filename.length) {
|
||||
filename = '/' + filename;
|
||||
} else {
|
||||
filename = '';
|
||||
}
|
||||
|
||||
// TODO: Could we somehow figure out if the collection requires login?
|
||||
var authToken = '';
|
||||
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") {
|
||||
if (options.auth !== false) {
|
||||
// Add reactive deps on the user
|
||||
Meteor.userId();
|
||||
|
||||
var authObject = {
|
||||
authToken: Accounts._storedLoginToken() || ''
|
||||
};
|
||||
|
||||
// If it's a number, we use that as the expiration time (in seconds)
|
||||
if (options.auth === +options.auth) {
|
||||
authObject.expiration = FS.HTTP.now() + options.auth * 1000;
|
||||
}
|
||||
|
||||
// Set the authToken
|
||||
var authString = JSON.stringify(authObject);
|
||||
authToken = FS.Utility.btoa(authString);
|
||||
}
|
||||
} else if (typeof options.auth === "string") {
|
||||
// If the user supplies auth token the user will be responsible for
|
||||
// updating
|
||||
authToken = options.auth;
|
||||
}
|
||||
|
||||
// Construct query string
|
||||
var params = {};
|
||||
if (authToken !== '') {
|
||||
params.token = authToken;
|
||||
}
|
||||
if (options.download) {
|
||||
params.download = true;
|
||||
}
|
||||
if (options.store) {
|
||||
// We use options.store here instead of storeName because we want to omit the queryString
|
||||
// whenever possible, allowing users to have "clean" URLs if they want. The server will
|
||||
// assume the first store defined on the server, which means that we are assuming that
|
||||
// the first on the client is also the first on the server. If that's not the case, the
|
||||
// store option should be supplied.
|
||||
params.store = options.store;
|
||||
}
|
||||
var queryString = FS.Utility.encodeParams(params);
|
||||
if (queryString.length) {
|
||||
queryString = '?' + queryString;
|
||||
}
|
||||
|
||||
// Determine which URL to use
|
||||
var area;
|
||||
if (options.metadata) {
|
||||
area = '/record';
|
||||
} else {
|
||||
area = '/files';
|
||||
}
|
||||
|
||||
// Construct and return the http method url
|
||||
return baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.url Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.url = function(options) {
|
||||
self = this;
|
||||
return rootUrlPathPrefix + self.urlRelative(options);
|
||||
};
|
||||
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
getHeaders = [];
|
||||
getHeadersByCollection = {};
|
||||
|
||||
var contentDisposition = Npm.require('content-disposition');
|
||||
|
||||
FS.HTTP.Handlers = {};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Del
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP DEL request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Del = function httpDelHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return
|
||||
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId);
|
||||
|
||||
/*
|
||||
* From the DELETE spec:
|
||||
* A successful response SHOULD be 200 (OK) if the response includes an
|
||||
* entity describing the status, 202 (Accepted) if the action has not
|
||||
* yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
* but the response does not include an entity.
|
||||
*/
|
||||
self.setStatusCode(200);
|
||||
|
||||
return {
|
||||
deleted: !!ref.file.remove()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.GetList
|
||||
* @public
|
||||
* @returns {Object} response
|
||||
*
|
||||
* HTTP GET file list request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.GetList = function httpGetListHandler() {
|
||||
// Not Yet Implemented
|
||||
// Need to check publications and return file list based on
|
||||
// what user is allowed to see
|
||||
};
|
||||
|
||||
/*
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
*/
|
||||
var requestRange = function(req, fileSize) {
|
||||
if (req) {
|
||||
if (req.headers) {
|
||||
var rangeString = req.headers.range;
|
||||
|
||||
// Make sure range is a string
|
||||
if (rangeString === ''+rangeString) {
|
||||
|
||||
// range will be in the format "bytes=0-32767"
|
||||
var parts = rangeString.split('=');
|
||||
var unit = parts[0];
|
||||
|
||||
// Make sure parts consists of two strings and range is of type "byte"
|
||||
if (parts.length == 2 && unit == 'bytes') {
|
||||
// Parse the range
|
||||
var range = parts[1].split('-');
|
||||
var start = Number(range[0]);
|
||||
var end = Number(range[1]);
|
||||
|
||||
// Fix invalid ranges?
|
||||
if (range[0] != start) start = 0;
|
||||
if (range[1] != end || !end) end = fileSize - 1;
|
||||
|
||||
// Make sure range consists of a start and end point of numbers and start is less than end
|
||||
if (start < end) {
|
||||
|
||||
var partSize = 0 - start + end + 1;
|
||||
|
||||
// Return the parsed range
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
length: partSize,
|
||||
size: fileSize,
|
||||
unit: unit,
|
||||
partial: (partSize < fileSize)
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new Meteor.Error(416, "Requested Range Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// The first part should be bytes
|
||||
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// No range found
|
||||
}
|
||||
|
||||
} else {
|
||||
// throw new Error('No request headers set for _parseRange function');
|
||||
}
|
||||
} else {
|
||||
throw new Error('No request object passed to _parseRange function');
|
||||
}
|
||||
|
||||
return {
|
||||
start: 0,
|
||||
end: fileSize - 1,
|
||||
length: fileSize,
|
||||
size: fileSize,
|
||||
unit: 'bytes',
|
||||
partial: false
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Get
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP GET request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Get = function httpGetHandler(ref) {
|
||||
var self = this;
|
||||
// Once we have the file, we can test allow/deny validators
|
||||
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?
|
||||
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/);
|
||||
|
||||
var storeName = ref.storeName;
|
||||
|
||||
// If no storeName was specified, use the first defined storeName
|
||||
if (typeof storeName !== "string") {
|
||||
// No store handed, we default to primary store
|
||||
storeName = ref.collection.primaryStore.name;
|
||||
}
|
||||
|
||||
// Get the storage reference
|
||||
var storage = ref.collection.storesLookup[storeName];
|
||||
|
||||
if (!storage) {
|
||||
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"');
|
||||
}
|
||||
|
||||
// Get the file
|
||||
var copyInfo = ref.file.copies[storeName];
|
||||
|
||||
if (!copyInfo) {
|
||||
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store');
|
||||
}
|
||||
|
||||
// Set the content type for file
|
||||
if (typeof copyInfo.type === "string") {
|
||||
self.setContentType(copyInfo.type);
|
||||
} else {
|
||||
self.setContentType('application/octet-stream');
|
||||
}
|
||||
|
||||
// Add 'Content-Disposition' header if requested a download/attachment URL
|
||||
if (typeof ref.download !== "undefined") {
|
||||
var filename = ref.filename || copyInfo.name;
|
||||
self.addHeader('Content-Disposition', contentDisposition(filename));
|
||||
} else {
|
||||
self.addHeader('Content-Disposition', 'inline');
|
||||
}
|
||||
|
||||
// Get the contents range from request
|
||||
var range = requestRange(self.request, copyInfo.size);
|
||||
|
||||
// Some browsers cope better if the content-range header is
|
||||
// still included even for the full file being returned.
|
||||
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
// If a chunk/range was requested instead of the whole file, serve that'
|
||||
if (range.partial) {
|
||||
self.setStatusCode(206, 'Partial Content');
|
||||
} else {
|
||||
self.setStatusCode(200, 'OK');
|
||||
}
|
||||
|
||||
// Add any other global custom headers and collection-specific custom headers
|
||||
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) {
|
||||
self.addHeader(header[0], header[1]);
|
||||
});
|
||||
|
||||
// Inform clients about length (or chunk length in case of ranges)
|
||||
self.addHeader('Content-Length', range.length);
|
||||
|
||||
// Last modified header (updatedAt from file info)
|
||||
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());
|
||||
|
||||
// Inform clients that we accept ranges for resumable chunked downloads
|
||||
self.addHeader('Accept-Ranges', range.unit);
|
||||
|
||||
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end});
|
||||
|
||||
readStream.on('error', function(err) {
|
||||
// Send proper error message on get error
|
||||
if (err.message && err.statusCode) {
|
||||
self.Error(new Meteor.Error(err.statusCode, err.message));
|
||||
} else {
|
||||
self.Error(new Meteor.Error(503, 'Service unavailable'));
|
||||
}
|
||||
});
|
||||
|
||||
readStream.pipe(self.createWriteStream());
|
||||
};
|
||||
|
||||
// File with unicode or other encodings filename can upload to server susscessfully,
|
||||
// but when download, the HTTP header "Content-Disposition" cannot accept
|
||||
// characters other than ASCII, the filename should be converted to binary or URI encoded.
|
||||
// https://github.com/wekan/wekan/issues/784
|
||||
const originalHandler = FS.HTTP.Handlers.Get;
|
||||
FS.HTTP.Handlers.Get = function (ref) {
|
||||
try {
|
||||
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
|
||||
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
|
||||
ref.filename = encodeURIComponent(ref.filename);
|
||||
} else if(userAgent.indexOf('firefox') >= 0) {
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
} else {
|
||||
/* safari*/
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
}
|
||||
} catch (ex){
|
||||
ref.filename = ref.filename;
|
||||
}
|
||||
return originalHandler.call(this, ref);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutInsert
|
||||
* @public
|
||||
* @returns {Object} response object with _id property
|
||||
*
|
||||
* HTTP PUT file insert request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
FS.debug && console.log("HTTP PUT (insert) handler");
|
||||
|
||||
// Create the nice FS.File
|
||||
var fileObj = new FS.File();
|
||||
|
||||
// Set its name
|
||||
fileObj.name(opts.filename || null);
|
||||
|
||||
// Attach the readstream as the file's data
|
||||
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
|
||||
|
||||
// Validate with insert allow/deny
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId);
|
||||
|
||||
// Insert file into collection, triggering readStream storage
|
||||
ref.collection.insert(fileObj);
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
// Return the new file id
|
||||
return {_id: fileObj._id};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutUpdate
|
||||
* @public
|
||||
* @returns {Object} response object with _id and chunk properties
|
||||
*
|
||||
* HTTP PUT file update chunk request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
var chunk = parseInt(opts.chunk, 10);
|
||||
if (isNaN(chunk)) chunk = 0;
|
||||
|
||||
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);
|
||||
|
||||
// Validate with insert allow/deny; also mounts and retrieves the file
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId);
|
||||
|
||||
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
return { _id: ref.file._id, chunk: chunk };
|
||||
};
|
||||
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
var path = Npm.require("path");
|
||||
|
||||
HTTP.publishFormats({
|
||||
fileRecordFormat: function (input) {
|
||||
// Set the method scope content type to json
|
||||
this.setContentType('application/json');
|
||||
if (FS.Utility.isArray(input)) {
|
||||
return EJSON.stringify(FS.Utility.map(input, function (obj) {
|
||||
return FS.Utility.cloneFileRecord(obj);
|
||||
}));
|
||||
} else {
|
||||
return EJSON.stringify(FS.Utility.cloneFileRecord(input));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setHeadersForGet
|
||||
* @public
|
||||
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
|
||||
if (typeof collections === "string") {
|
||||
collections = [collections];
|
||||
}
|
||||
if (collections) {
|
||||
FS.Utility.each(collections, function(collectionName) {
|
||||
getHeadersByCollection[collectionName] = headers || [];
|
||||
});
|
||||
} else {
|
||||
getHeaders = headers || [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.publish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @param {Function} func - Publish function that returns a cursor.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Publishes all documents returned by the cursor at a GET URL
|
||||
* with the format baseUrl/record/collectionName. The publish
|
||||
* function `this` is similar to normal `Meteor.publish`.
|
||||
*/
|
||||
FS.HTTP.publish = function fsHttpPublish(collection, func) {
|
||||
var name = baseUrl + '/record/' + collection.name;
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.publish({
|
||||
name: name,
|
||||
defaultFormat: 'fileRecordFormat',
|
||||
collection: collection,
|
||||
collectionGet: true,
|
||||
collectionPost: false,
|
||||
documentGet: true,
|
||||
documentPut: false,
|
||||
documentDelete: false
|
||||
}, func);
|
||||
|
||||
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unpublish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
*/
|
||||
FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.unpublish(baseUrl + '/record/' + collection.name);
|
||||
};
|
||||
|
||||
_existingMountPoints = {};
|
||||
|
||||
/**
|
||||
* @method defaultSelectorFunction
|
||||
* @private
|
||||
* @returns { collection, file }
|
||||
*
|
||||
* This is the default selector function
|
||||
*/
|
||||
var defaultSelectorFunction = function() {
|
||||
var self = this;
|
||||
// Selector function
|
||||
//
|
||||
// This function will have to return the collection and the
|
||||
// file. If file not found undefined is returned - if null is returned the
|
||||
// search was not possible
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// Get the collection name from the url
|
||||
var collectionName = opts.collectionName;
|
||||
|
||||
// Get the id from the url
|
||||
var id = opts.id;
|
||||
|
||||
// Get the collection
|
||||
var collection = FS._collections[collectionName];
|
||||
|
||||
//if Mongo ObjectIds are used, then we need to use that in find statement
|
||||
if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') {
|
||||
// Get the file if possible else return null
|
||||
var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null;
|
||||
} else {
|
||||
var file = (id && collection)? collection.findOne({ _id: id }): null;
|
||||
}
|
||||
|
||||
|
||||
// Return the collection and the file
|
||||
return {
|
||||
collection: collection,
|
||||
file: file,
|
||||
storeName: opts.store,
|
||||
download: opts.download,
|
||||
filename: opts.filename
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* @method FS.HTTP.mount
|
||||
* @public
|
||||
* @param {array of string} mountPoints mount points to map rest functinality on
|
||||
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with
|
||||
*
|
||||
*/
|
||||
FS.HTTP.mount = function(mountPoints, selector_f) {
|
||||
// We take mount points as an array and we get a selector function
|
||||
var selectorFunction = selector_f || defaultSelectorFunction;
|
||||
|
||||
var accessPoint = {
|
||||
'stream': true,
|
||||
'auth': expirationAuth,
|
||||
'post': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// We dont support post - this would be normal insert eg. of filerecord?
|
||||
throw new Meteor.Error(501, "Not implemented", "Post is not supported");
|
||||
},
|
||||
'put': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will create a new FS.File instance and
|
||||
// insert the supplied data.
|
||||
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'get': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will return the published list of files ala
|
||||
// http.publish in json format
|
||||
return FS.HTTP.Handlers.GetList.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Get.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'delete': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Del.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var accessPoints = {};
|
||||
|
||||
// Add debug message
|
||||
FS.debug && console.log('Registered HTTP method URLs:');
|
||||
|
||||
FS.Utility.each(mountPoints, function(mountPoint) {
|
||||
// Couple mountpoint and accesspoint
|
||||
accessPoints[mountPoint] = accessPoint;
|
||||
// Remember our mountpoints
|
||||
_existingMountPoints[mountPoint] = mountPoint;
|
||||
// Add debug message
|
||||
FS.debug && console.log(mountPoint);
|
||||
});
|
||||
|
||||
// XXX: HTTP:methods should unmount existing mounts in case of overwriting?
|
||||
HTTP.methods(accessPoints);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unmount
|
||||
* @public
|
||||
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted
|
||||
*
|
||||
*/
|
||||
FS.HTTP.unmount = function(mountPoints) {
|
||||
// The mountPoints is optional, can be string or array if undefined then
|
||||
// _existingMountPoints will be used
|
||||
var unmountList;
|
||||
// Container for the mount points to unmount
|
||||
var unmountPoints = {};
|
||||
|
||||
if (typeof mountPoints === 'undefined') {
|
||||
// Use existing mount points - unmount all
|
||||
unmountList = _existingMountPoints;
|
||||
} else if (mountPoints === ''+mountPoints) {
|
||||
// Got a string
|
||||
unmountList = [mountPoints];
|
||||
} else if (mountPoints.length) {
|
||||
// Got an array
|
||||
unmountList = mountPoints;
|
||||
}
|
||||
|
||||
// If we have a list to unmount
|
||||
if (unmountList) {
|
||||
// Iterate over each item
|
||||
FS.Utility.each(unmountList, function(mountPoint) {
|
||||
// Check _existingMountPoints to make sure the mount point exists in our
|
||||
// context / was created by the FS.HTTP.mount
|
||||
if (_existingMountPoints[mountPoint]) {
|
||||
// Mark as unmount
|
||||
unmountPoints[mountPoint] = false;
|
||||
// Release
|
||||
delete _existingMountPoints[mountPoint];
|
||||
}
|
||||
});
|
||||
FS.debug && console.log('FS.HTTP.unmount:');
|
||||
FS.debug && console.log(unmountPoints);
|
||||
// Complete unmount
|
||||
HTTP.methods(unmountPoints);
|
||||
}
|
||||
};
|
||||
|
||||
// ### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
// *
|
||||
// baseUrl + '/files/:collectionName/:id/:filename',
|
||||
// baseUrl + '/files/:collectionName/:id',
|
||||
// baseUrl + '/files/:collectionName'
|
||||
//
|
||||
// Change/ replace the existing mount point by:
|
||||
// ```js
|
||||
// // unmount all existing
|
||||
// FS.HTTP.unmount();
|
||||
// // Create new mount point
|
||||
// FS.HTTP.mount([
|
||||
// '/cfs/files/:collectionName/:id/:filename',
|
||||
// '/cfs/files/:collectionName/:id',
|
||||
// '/cfs/files/:collectionName'
|
||||
// ]);
|
||||
// ```
|
||||
//
|
||||
mountUrls = function mountUrls() {
|
||||
// We unmount first in case we are calling this a second time
|
||||
FS.HTTP.unmount();
|
||||
|
||||
FS.HTTP.mount([
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
]);
|
||||
};
|
||||
|
||||
// Returns the userId from URL token
|
||||
var expirationAuth = function expirationAuth() {
|
||||
var self = this;
|
||||
|
||||
// Read the token from '/hello?token=base64'
|
||||
var encodedToken = self.query.token;
|
||||
|
||||
FS.debug && console.log("token: "+encodedToken);
|
||||
|
||||
if (!encodedToken || !Meteor.users) return false;
|
||||
|
||||
// Check the userToken before adding it to the db query
|
||||
// Set the this.userId
|
||||
var tokenString = FS.Utility.atob(encodedToken);
|
||||
|
||||
var tokenObject;
|
||||
try {
|
||||
tokenObject = JSON.parse(tokenString);
|
||||
} catch(err) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// XXX: Do some check here of the object
|
||||
var userToken = tokenObject.authToken;
|
||||
if (userToken !== ''+userToken) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// If we have an expiration token we should check that it's still valid
|
||||
if (tokenObject.expiration != null) {
|
||||
// check if its too old
|
||||
var now = Date.now();
|
||||
if (tokenObject.expiration < now) {
|
||||
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);
|
||||
throw new Meteor.Error(500, 'Expired token');
|
||||
}
|
||||
}
|
||||
|
||||
// We are not on a secure line - so we have to look up the user...
|
||||
var user = Meteor.users.findOne({
|
||||
$or: [
|
||||
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
|
||||
{'services.resume.loginTokens.token': userToken}
|
||||
]
|
||||
});
|
||||
|
||||
// Set the userId in the scope
|
||||
return user && user._id;
|
||||
};
|
||||
|
||||
HTTP.methods(
|
||||
{'/cfs/servertime': {
|
||||
get: function(data) {
|
||||
return Date.now().toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Unify client / server api
|
||||
FS.HTTP.now = function() {
|
||||
return Date.now();
|
||||
};
|
||||
|
||||
// Start up the basic mount points
|
||||
Meteor.startup(function () {
|
||||
mountUrls();
|
||||
});
|
||||
271
packages/wekan-cfs-access-point/api.md
Normal file
271
packages/wekan-cfs-access-point/api.md
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
## wekan-cfs-access-point Public API ##
|
||||
|
||||
CollectionFS, add ddp and http accesspoint capability
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
||||
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-common.js"](access-point-common.js) Where: {server|client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-handlers.js"](access-point-handlers.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="self.setStatusCode"></a>*self*.setStatusCode {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
From the DELETE spec:
|
||||
A successful response SHOULD be 200 (OK) if the response includes an
|
||||
entity describing the status, 202 (Accepted) if the action has not
|
||||
yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
but the response does not include an entity.
|
||||
```
|
||||
*This property __setStatusCode__ is defined in `self`*
|
||||
|
||||
> ```self.setStatusCode(200);``` [access-point-handlers.js:27](access-point-handlers.js#L27)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="requestRange"></a>requestRange {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
```
|
||||
*This property is private*
|
||||
|
||||
> ```var requestRange = function(req, fileSize) { ...``` [access-point-handlers.js:60](access-point-handlers.js#L60)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-server.js"](access-point-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="defaultSelectorFunction"></a>defaultSelectorFunction() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Returns__ *{ collection, file }*
|
||||
|
||||
|
||||
This is the default selector function
|
||||
|
||||
> ```var defaultSelectorFunction = function() { ...``` [access-point-server.js:87](access-point-server.js#L87)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
||||
65
packages/wekan-cfs-access-point/package.js
Normal file
65
packages/wekan-cfs-access-point/package.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-access-point',
|
||||
version: '0.1.50',
|
||||
summary: 'CollectionFS, add ddp and http accesspoint capability',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-access-point.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"content-disposition": "0.5.0"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
// This imply is needed for tests, and is technically probably correct anyway.
|
||||
api.imply([
|
||||
'wekan-cfs-base-package'
|
||||
]);
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-file@0.1.16',
|
||||
//Core packages
|
||||
'check',
|
||||
'ejson',
|
||||
//Other packages
|
||||
'wekan-cfs-http-methods@0.0.29',
|
||||
'wekan-cfs-http-publish@0.0.13'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-handlers.js',
|
||||
'access-point-server.js'
|
||||
], 'server');
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-client.js'
|
||||
], 'client');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-access-point',
|
||||
'wekan-cfs-standard-packages@0.0.2',
|
||||
'wekan-cfs-gridfs@0.0.0',
|
||||
//Core packages
|
||||
'test-helpers',
|
||||
'http',
|
||||
'tinytest',
|
||||
'underscore',
|
||||
'ejson',
|
||||
'ordered-dict',
|
||||
'random',
|
||||
'deps'
|
||||
]);
|
||||
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
});
|
||||
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-access-point - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Meteor.subscribe("img");
|
||||
|
||||
var id;
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - addTestImage', function(test, onComplete) {
|
||||
Meteor.call('addTestImage', function(err, result) {
|
||||
id = result;
|
||||
test.equal(typeof id, "string", "Test image was not inserted properly");
|
||||
//Don't continue until the data has been stored
|
||||
Deps.autorun(function (c) {
|
||||
var img = Images.findOne(id);
|
||||
if (img && img.hasCopy('gridList')) {
|
||||
onComplete();
|
||||
c.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET list of files in collection', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images'), function(err, result) {
|
||||
// Test the length of array result
|
||||
var len = result.data && result.data.length;
|
||||
test.isTrue(!!len, 'Result was empty');
|
||||
// Get the object
|
||||
var obj = result.data && result.data[0] || {};
|
||||
test.equal(obj._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET filerecord', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
// Get the object
|
||||
var obj = result.data;
|
||||
test.equal(typeof obj, "object", "Expected object data");
|
||||
test.equal(obj._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET file itself', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.isTrue(!!result.content, "Expected content in response");
|
||||
console.log(result);
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT new file data (update)', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT insert a new file', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images'), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - DELETE filerecord and data', function(test, onComplete) {
|
||||
|
||||
HTTP.del(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
|
||||
// Make sure it's gone
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
test.isTrue(!!err, 'Expected 404 error');
|
||||
test.equal(result.statusCode, 404, "Expected 404 response");
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//TODO test FS.File.prototype.url method with various options
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
FS.debug = true;
|
||||
|
||||
Tinytest.add('cfs-access-point - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Images.allow({
|
||||
insert: function() {
|
||||
return true;
|
||||
},
|
||||
update: function() {
|
||||
return true;
|
||||
},
|
||||
remove: function() {
|
||||
return true;
|
||||
},
|
||||
download: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.publish("img", function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
addTestImage: function() {
|
||||
Images.remove({});
|
||||
var url = "http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg";
|
||||
var fsFile = Images.insert(url);
|
||||
return fsFile._id;
|
||||
}
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
5
packages/wekan-cfs-base-package/.travis.yml
Normal file
5
packages/wekan-cfs-base-package/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
20
packages/wekan-cfs-base-package/LICENSE.md
Normal file
20
packages/wekan-cfs-base-package/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
packages/wekan-cfs-base-package/README.md
Normal file
11
packages/wekan-cfs-base-package/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
wekan-cfs-base-package
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
||||
|
||||
This package provides the `FS` namespace and helper methods used by many
|
||||
CollectionFS packages.
|
||||
213
packages/wekan-cfs-base-package/api.md
Normal file
213
packages/wekan-cfs-base-package/api.md
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
## cfs-base-package Public API ##
|
||||
|
||||
CollectionFS, Base package
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
#############################################################################
|
||||
|
||||
HELPERS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __cloneFileRecord__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __full__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Set `true` to prevent certain properties from being omitted from the clone.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
Cloned filerecord
|
||||
|
||||
|
||||
Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
it's an FS.File instance, but which we never want to be part of the stored
|
||||
filerecord.
|
||||
|
||||
This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
to specify whatever additional properties they wish.
|
||||
|
||||
In general, we expect the following whitelist properties used by the internal and
|
||||
external APIs:
|
||||
|
||||
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
|
||||
Those properties, and any additional properties added by the user, should be present
|
||||
in the returned object, which is suitable for inserting into the backing collection or
|
||||
extending an FS.File instance.
|
||||
|
||||
|
||||
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __f__ *{Function}* (Optional)
|
||||
|
||||
A callback function, if you have one. Can be undefined or null.
|
||||
|
||||
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
|
||||
|
||||
Error or error message (string)
|
||||
|
||||
|
||||
__Returns__ *{Any}*
|
||||
the callback result if any
|
||||
|
||||
|
||||
Handle Error, creates an Error instance with the given text. If callback is
|
||||
a function, passes the error to that function. Otherwise throws it. Useful
|
||||
for dealing with errors in methods that optionally accept a callback.
|
||||
|
||||
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.noop"></a>*fsUtility*.noop() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __noop__ is defined in `FS.Utility`*
|
||||
|
||||
Use this to hand a no operation / empty function
|
||||
|
||||
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL that may or may not have an extension.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The extension or an empty string if no extension found.
|
||||
|
||||
|
||||
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename that may or may not already have an extension.
|
||||
|
||||
* __ext__ *{String}*
|
||||
|
||||
An extension without leading period, which you want to be the new extension on `name`.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename with changed extension.
|
||||
|
||||
|
||||
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __binaryToBuffer__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Uint8Array}*
|
||||
|
||||
__Returns__ *{Buffer}*
|
||||
|
||||
|
||||
Converts a Uint8Array instance to a Node Buffer instance
|
||||
|
||||
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __bufferToBinary__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer}*
|
||||
|
||||
__Returns__ *{Uint8Array}*
|
||||
|
||||
|
||||
Converts a Node Buffer instance to a Uint8Array instance
|
||||
|
||||
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __eachFile__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __e__ *{[Event](#Event)}*
|
||||
|
||||
Browser event
|
||||
|
||||
* __f__ *{Function}*
|
||||
|
||||
Function to run for each file found in the event.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Utility for iteration over files in event
|
||||
|
||||
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)
|
||||
|
||||
|
||||
51
packages/wekan-cfs-base-package/base-client.js
Normal file
51
packages/wekan-cfs-base-package/base-client.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
//XXX not sure this is still working properly?
|
||||
FS.Utility.connectionLogin = function(connection) {
|
||||
// We check if the accounts package is installed, since we depend on
|
||||
// `Meteor.userId()`
|
||||
if (typeof Accounts !== 'undefined') {
|
||||
// Monitor logout from main connection
|
||||
Meteor.startup(function() {
|
||||
Tracker.autorun(function() {
|
||||
var userId = Meteor.userId();
|
||||
if (userId) {
|
||||
connection.onReconnect = function() {
|
||||
var token = Accounts._storedLoginToken();
|
||||
connection.apply('login', [{resume: token}], function(err, result) {
|
||||
if (!err && result) {
|
||||
connection.setUserId(result.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
} else {
|
||||
connection.onReconnect = null;
|
||||
connection.setUserId(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.eachFile
|
||||
* @public
|
||||
* @param {Event} e - Browser event
|
||||
* @param {Function} f - Function to run for each file found in the event.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Utility for iteration over files in event
|
||||
*/
|
||||
FS.Utility.eachFile = function(e, f) {
|
||||
var evt = (e.originalEvent || e);
|
||||
|
||||
var files = evt.target.files;
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
files = evt.dataTransfer ? evt.dataTransfer.files : [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
f(files[i], i);
|
||||
}
|
||||
};
|
||||
317
packages/wekan-cfs-base-package/base-common.js
Normal file
317
packages/wekan-cfs-base-package/base-common.js
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// Exported namespace
|
||||
FS = {};
|
||||
|
||||
// namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead?
|
||||
FS.Store = {
|
||||
GridFS: function () {
|
||||
throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.');
|
||||
},
|
||||
FileSystem: function () {
|
||||
throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.');
|
||||
},
|
||||
S3: function () {
|
||||
throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.');
|
||||
},
|
||||
WABS: function () {
|
||||
throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.');
|
||||
},
|
||||
Dropbox: function () {
|
||||
throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.');
|
||||
}
|
||||
};
|
||||
|
||||
// namespace for access points
|
||||
FS.AccessPoint = {};
|
||||
|
||||
// namespace for utillities
|
||||
FS.Utility = {};
|
||||
|
||||
// A general place for any package to store global config settings
|
||||
FS.config = {};
|
||||
|
||||
// An internal collection reference
|
||||
FS._collections = {};
|
||||
|
||||
// Test scope
|
||||
_Utility = {};
|
||||
|
||||
// #############################################################################
|
||||
//
|
||||
// HELPERS
|
||||
//
|
||||
// #############################################################################
|
||||
|
||||
/** @method _Utility.defaultZero
|
||||
* @private
|
||||
* @param {Any} val Returns number or 0 if value is a falsy
|
||||
*/
|
||||
_Utility.defaultZero = function(val) {
|
||||
return +(val || 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.cloneFileRecord
|
||||
* @public
|
||||
* @param {FS.File|FS.Collection filerecord} rec
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone.
|
||||
* @returns {Object} Cloned filerecord
|
||||
*
|
||||
* Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
* it's an FS.File instance, but which we never want to be part of the stored
|
||||
* filerecord.
|
||||
*
|
||||
* This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
* to specify whatever additional properties they wish.
|
||||
*
|
||||
* In general, we expect the following whitelist properties used by the internal and
|
||||
* external APIs:
|
||||
*
|
||||
* _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
*
|
||||
* Those properties, and any additional properties added by the user, should be present
|
||||
* in the returned object, which is suitable for inserting into the backing collection or
|
||||
* extending an FS.File instance.
|
||||
*
|
||||
*/
|
||||
FS.Utility.cloneFileRecord = function(rec, options) {
|
||||
options = options || {};
|
||||
var result = {};
|
||||
// We use this method for two purposes. If using it to clone one FS.File into another, then
|
||||
// we want a full clone. But if using it to get a filerecord object for inserting into the
|
||||
// internal collection, then there are certain properties we want to omit so that they aren't
|
||||
// stored in the collection.
|
||||
var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform'];
|
||||
for (var prop in rec) {
|
||||
if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) {
|
||||
result[prop] = rec[prop];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.defaultCallback
|
||||
* @public
|
||||
* @param {Error} [err]
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Can be used as a default callback for client methods that need a callback.
|
||||
* Simply throws the provided error if there is one.
|
||||
*/
|
||||
FS.Utility.defaultCallback = function defaultCallback(err) {
|
||||
if (err) {
|
||||
// Show gentle error if Meteor error
|
||||
if (err instanceof Meteor.Error) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
// Normal error, just throw error
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.defaultCallback
|
||||
* @public
|
||||
* @param {Function} [f] A callback function, if you have one. Can be undefined or null.
|
||||
* @param {Meteor.Error | Error | String} [err] Error or error message (string)
|
||||
* @returns {Any} the callback result if any
|
||||
*
|
||||
* Handle Error, creates an Error instance with the given text. If callback is
|
||||
* a function, passes the error to that function. Otherwise throws it. Useful
|
||||
* for dealing with errors in methods that optionally accept a callback.
|
||||
*/
|
||||
FS.Utility.handleError = function(f, err, result) {
|
||||
// Set callback
|
||||
var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback;
|
||||
// Set the err
|
||||
var error = (err === ''+err)? new Error(err) : err;
|
||||
// callback
|
||||
return callback(error, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method FS.Utility.noop
|
||||
* @public
|
||||
* Use this to hand a no operation / empty function
|
||||
*/
|
||||
FS.Utility.noop = function() {};
|
||||
|
||||
/**
|
||||
* @method validateAction
|
||||
* @private
|
||||
* @param {Object} validators - The validators object to use, with `deny` and `allow` properties.
|
||||
* @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators.
|
||||
* @param {String} userId - The ID of the user who is attempting the action.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Throws a "400-Bad Request" Meteor error if the file is not mounted or
|
||||
* a "400-Access denied" Meteor error if the action is not allowed.
|
||||
*/
|
||||
FS.Utility.validateAction = function validateAction(validators, fileObj, userId) {
|
||||
var denyValidators = validators.deny;
|
||||
var allowValidators = validators.allow;
|
||||
|
||||
// If insecure package is used and there are no validators defined,
|
||||
// allow the action.
|
||||
if (typeof Package === 'object'
|
||||
&& Package.insecure
|
||||
&& denyValidators.length + allowValidators.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If already mounted, validators should receive a fileObj
|
||||
// that is fully populated
|
||||
if (fileObj.isMounted()) {
|
||||
fileObj.getFileRecord();
|
||||
}
|
||||
|
||||
// Any deny returns true means denied.
|
||||
if (_.any(denyValidators, function(validator) {
|
||||
return validator(userId, fileObj);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (_.all(allowValidators, function(validator) {
|
||||
return !validator(userId, fileObj);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.getFileName
|
||||
* @private
|
||||
* @param {String} name - A filename, filepath, or URL
|
||||
* @returns {String} The filename without the URL, filepath, or query string
|
||||
*/
|
||||
FS.Utility.getFileName = function utilGetFileName(name) {
|
||||
// in case it's a URL, strip off potential query string
|
||||
// should have no effect on filepath
|
||||
name = name.split('?')[0];
|
||||
// strip off beginning path or url
|
||||
var lastSlash = name.lastIndexOf('/');
|
||||
if (lastSlash !== -1) {
|
||||
name = name.slice(lastSlash + 1);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.getFileExtension
|
||||
* @public
|
||||
* @param {String} name - A filename, filepath, or URL that may or may not have an extension.
|
||||
* @returns {String} The extension or an empty string if no extension found.
|
||||
*/
|
||||
FS.Utility.getFileExtension = function utilGetFileExtension(name) {
|
||||
name = FS.Utility.getFileName(name);
|
||||
// Seekout the last '.' if found
|
||||
var found = name.lastIndexOf('.');
|
||||
// Return the extension if found else ''
|
||||
// If found is -1, we return '' because there is no extension
|
||||
// If found is 0, we return '' because it's a hidden file
|
||||
return (found > 0 ? name.slice(found + 1).toLowerCase() : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.setFileExtension
|
||||
* @public
|
||||
* @param {String} name - A filename that may or may not already have an extension.
|
||||
* @param {String} ext - An extension without leading period, which you want to be the new extension on `name`.
|
||||
* @returns {String} The filename with changed extension.
|
||||
*/
|
||||
FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) {
|
||||
if (!name || !name.length) {
|
||||
return name;
|
||||
}
|
||||
var currentExt = FS.Utility.getFileExtension(name);
|
||||
if (currentExt.length) {
|
||||
name = name.slice(0, currentExt.length * -1) + ext;
|
||||
} else {
|
||||
name = name + '.' + ext;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
/*
|
||||
* Borrowed these from http package
|
||||
*/
|
||||
FS.Utility.encodeParams = function encodeParams(params) {
|
||||
var buf = [];
|
||||
_.each(params, function(value, key) {
|
||||
if (buf.length)
|
||||
buf.push('&');
|
||||
buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value));
|
||||
});
|
||||
return buf.join('').replace(/%20/g, '+');
|
||||
};
|
||||
|
||||
FS.Utility.encodeString = function encodeString(str) {
|
||||
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
};
|
||||
|
||||
/*
|
||||
* btoa and atob shims for client and server
|
||||
*/
|
||||
|
||||
FS.Utility._btoa = function _fsUtility_btoa(str) {
|
||||
var buffer;
|
||||
|
||||
if (str instanceof Buffer) {
|
||||
buffer = str;
|
||||
} else {
|
||||
buffer = new Buffer(str.toString(), 'binary');
|
||||
}
|
||||
|
||||
return buffer.toString('base64');
|
||||
};
|
||||
|
||||
FS.Utility.btoa = function fsUtility_btoa(str) {
|
||||
if (typeof btoa === 'function') {
|
||||
// Client
|
||||
return btoa(str);
|
||||
} else if (typeof Buffer !== 'undefined') {
|
||||
// Server
|
||||
return FS.Utility._btoa(str);
|
||||
} else {
|
||||
throw new Error('FS.Utility.btoa: Cannot base64 encode on your system');
|
||||
}
|
||||
};
|
||||
|
||||
FS.Utility._atob = function _fsUtility_atob(str) {
|
||||
return new Buffer(str, 'base64').toString('binary');
|
||||
};
|
||||
|
||||
FS.Utility.atob = function fsUtility_atob(str) {
|
||||
if (typeof atob === 'function') {
|
||||
// Client
|
||||
return atob(str);
|
||||
} else if (typeof Buffer !== 'undefined') {
|
||||
// Server
|
||||
return FS.Utility._atob(str);
|
||||
} else {
|
||||
throw new Error('FS.Utility.atob: Cannot base64 encode on your system');
|
||||
}
|
||||
};
|
||||
|
||||
// Api wrap for 3party libs like underscore
|
||||
FS.Utility.extend = _.extend;
|
||||
|
||||
FS.Utility.each = _.each;
|
||||
|
||||
FS.Utility.isEmpty = _.isEmpty;
|
||||
|
||||
FS.Utility.indexOf = _.indexOf;
|
||||
|
||||
FS.Utility.isArray = _.isArray;
|
||||
|
||||
FS.Utility.map = _.map;
|
||||
|
||||
FS.Utility.once = _.once;
|
||||
|
||||
FS.Utility.include = _.include;
|
||||
|
||||
FS.Utility.size = _.size;
|
||||
95
packages/wekan-cfs-base-package/base-server.js
Normal file
95
packages/wekan-cfs-base-package/base-server.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* @method FS.Utility.binaryToBuffer
|
||||
* @public
|
||||
* @param {Uint8Array} data
|
||||
* @returns {Buffer}
|
||||
*
|
||||
* Converts a Uint8Array instance to a Node Buffer instance
|
||||
*/
|
||||
FS.Utility.binaryToBuffer = function(data) {
|
||||
var len = data.length;
|
||||
var buffer = new Buffer(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
buffer[i] = data[i];
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.bufferToBinary
|
||||
* @public
|
||||
* @param {Buffer} data
|
||||
* @returns {Uint8Array}
|
||||
*
|
||||
* Converts a Node Buffer instance to a Uint8Array instance
|
||||
*/
|
||||
FS.Utility.bufferToBinary = function(data) {
|
||||
var len = data.length;
|
||||
var binary = EJSON.newBinary(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary[i] = data[i];
|
||||
}
|
||||
return binary;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.safeCallback
|
||||
* @public
|
||||
* @param {Function} callback
|
||||
* @returns {Function}
|
||||
*
|
||||
* Makes a callback safe for Meteor code
|
||||
*/
|
||||
FS.Utility.safeCallback = function (callback) {
|
||||
return Meteor.bindEnvironment(callback, function(err) { throw err; });
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.safeStream
|
||||
* @public
|
||||
* @param {Stream} nodestream
|
||||
* @returns {Stream}
|
||||
*
|
||||
* Adds `safeOn` and `safeOnce` methods to a NodeJS Stream
|
||||
* object. These are the same as `on` and `once`, except
|
||||
* that the callback is wrapped for use in Meteor.
|
||||
*/
|
||||
FS.Utility.safeStream = function(nodestream) {
|
||||
if (!nodestream || typeof nodestream.on !== 'function')
|
||||
throw new Error('FS.Utility.safeStream requires a NodeJS Stream');
|
||||
|
||||
// Create Meteor safe events
|
||||
nodestream.safeOn = function(name, callback) {
|
||||
return nodestream.on(name, FS.Utility.safeCallback(callback));
|
||||
};
|
||||
|
||||
// Create Meteor safe events
|
||||
nodestream.safeOnce = function(name, callback) {
|
||||
return nodestream.once(name, FS.Utility.safeCallback(callback));
|
||||
};
|
||||
|
||||
// Return the modified stream - modified anyway
|
||||
return nodestream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Utility.eachFileFromPath
|
||||
* @public
|
||||
* @param {String} p - Server path
|
||||
* @param {Function} f - Function to run for each file found in the path.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Utility for iteration over files from path on server
|
||||
*/
|
||||
FS.Utility.eachFileFromPath = function(p, f) {
|
||||
var fs = Npm.require('fs');
|
||||
var path = Npm.require('path');
|
||||
var files = fs.readdirSync(p);
|
||||
files.map(function (file) {
|
||||
return path.join(p, file);
|
||||
}).filter(function (filePath) {
|
||||
return fs.statSync(filePath).isFile() && path.basename(filePath)[0] !== '.';
|
||||
}).forEach(function (filePath) {
|
||||
f(filePath);
|
||||
});
|
||||
};
|
||||
293
packages/wekan-cfs-base-package/internal.api.md
Normal file
293
packages/wekan-cfs-base-package/internal.api.md
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-common.js"](base-common.js) Where: {server|client}__
|
||||
|
||||
***
|
||||
|
||||
#############################################################################
|
||||
|
||||
HELPERS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="_Utility.defaultZero"></a>*_utility*.defaultZero(val) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __defaultZero__ is defined in `_Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __val__ *{Any}*
|
||||
|
||||
Returns number or 0 if value is a falsy
|
||||
|
||||
|
||||
> ```_Utility.defaultZero = function(val) { ...``` [base-common.js:42](base-common.js#L42)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __cloneFileRecord__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __full__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Set `true` to prevent certain properties from being omitted from the clone.
|
||||
|
||||
|
||||
__Returns__ *{Object}*
|
||||
Cloned filerecord
|
||||
|
||||
|
||||
Makes a shallow clone of `rec`, filtering out some properties that might be present if
|
||||
it's an FS.File instance, but which we never want to be part of the stored
|
||||
filerecord.
|
||||
|
||||
This is a blacklist clone rather than a whitelist because we want the user to be able
|
||||
to specify whatever additional properties they wish.
|
||||
|
||||
In general, we expect the following whitelist properties used by the internal and
|
||||
external APIs:
|
||||
|
||||
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
|
||||
|
||||
Those properties, and any additional properties added by the user, should be present
|
||||
in the returned object, which is suitable for inserting into the backing collection or
|
||||
extending an FS.File instance.
|
||||
|
||||
|
||||
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __defaultCallback__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __f__ *{Function}* (Optional)
|
||||
|
||||
A callback function, if you have one. Can be undefined or null.
|
||||
|
||||
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
|
||||
|
||||
Error or error message (string)
|
||||
|
||||
|
||||
__Returns__ *{Any}*
|
||||
the callback result if any
|
||||
|
||||
|
||||
Handle Error, creates an Error instance with the given text. If callback is
|
||||
a function, passes the error to that function. Otherwise throws it. Useful
|
||||
for dealing with errors in methods that optionally accept a callback.
|
||||
|
||||
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.noop"></a>*fsUtility*.noop() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __noop__ is defined in `FS.Utility`*
|
||||
|
||||
Use this to hand a no operation / empty function
|
||||
|
||||
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="validateAction"></a>validateAction(validators, fileObj, userId) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __validators__ *{Object}*
|
||||
|
||||
The validators object to use, with `deny` and `allow` properties.
|
||||
|
||||
* __fileObj__ *{[FS.File](#FS.File)}*
|
||||
|
||||
Mounted or mountable file object to be passed to validators.
|
||||
|
||||
* __userId__ *{String}*
|
||||
|
||||
The ID of the user who is attempting the action.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Throws a "400-Bad Request" Meteor error if the file is not mounted or
|
||||
a "400-Access denied" Meteor error if the action is not allowed.
|
||||
|
||||
> ```FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { ...``` [base-common.js:147](base-common.js#L147)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileName"></a>*fsUtility*.getFileName(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getFileName__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename without the URL, filepath, or query string
|
||||
|
||||
|
||||
> ```FS.Utility.getFileName = function utilGetFileName(name) { ...``` [base-common.js:187](base-common.js#L187)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __getFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename, filepath, or URL that may or may not have an extension.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The extension or an empty string if no extension found.
|
||||
|
||||
|
||||
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setFileExtension__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{String}*
|
||||
|
||||
A filename that may or may not already have an extension.
|
||||
|
||||
* __ext__ *{String}*
|
||||
|
||||
An extension without leading period, which you want to be the new extension on `name`.
|
||||
|
||||
|
||||
__Returns__ *{String}*
|
||||
The filename with changed extension.
|
||||
|
||||
|
||||
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-server.js"](base-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __binaryToBuffer__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Uint8Array}*
|
||||
|
||||
__Returns__ *{Buffer}*
|
||||
|
||||
|
||||
Converts a Uint8Array instance to a Node Buffer instance
|
||||
|
||||
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __bufferToBinary__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer}*
|
||||
|
||||
__Returns__ *{Uint8Array}*
|
||||
|
||||
|
||||
Converts a Node Buffer instance to a Uint8Array instance
|
||||
|
||||
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["base-client.js"](base-client.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __eachFile__ is defined in `FS.Utility`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __e__ *{[Event](#Event)}*
|
||||
|
||||
Browser event
|
||||
|
||||
* __f__ *{Function}*
|
||||
|
||||
Function to run for each file found in the event.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Utility for iteration over files in event
|
||||
|
||||
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)
|
||||
|
||||
|
||||
37
packages/wekan-cfs-base-package/package.js
Normal file
37
packages/wekan-cfs-base-package/package.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
Package.describe({
|
||||
version: '0.0.30',
|
||||
name: 'wekan-cfs-base-package',
|
||||
summary: 'CollectionFS, Base package',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-base-package.git'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['deps', 'underscore', 'ejson']);
|
||||
|
||||
if (api.export) {
|
||||
api.export('FS');
|
||||
api.export('_Utility', { testOnly: true });
|
||||
}
|
||||
|
||||
api.addFiles([
|
||||
'base-common.js',
|
||||
'base-server.js'
|
||||
], 'server');
|
||||
|
||||
api.addFiles([
|
||||
'polyfill.base64.js',
|
||||
'base-common.js',
|
||||
'base-client.js'
|
||||
], 'client');
|
||||
});
|
||||
|
||||
// Package.on_test(function (api) {
|
||||
// api.use(['wekan-cfs-base-package', 'cfs-file']);
|
||||
// api.use('test-helpers', 'server');
|
||||
// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
|
||||
// 'random', 'deps']);
|
||||
|
||||
// api.add_files('tests/common-tests.js', ['client', 'server']);
|
||||
// });
|
||||
179
packages/wekan-cfs-base-package/polyfill.base64.js
Normal file
179
packages/wekan-cfs-base-package/polyfill.base64.js
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Nick Galbreath
|
||||
* http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* base64 encode/decode compatible with window.btoa/atob
|
||||
*
|
||||
* window.atob/btoa is a Firefox extension to convert binary data (the "b")
|
||||
* to base64 (ascii, the "a").
|
||||
*
|
||||
* It is also found in Safari and Chrome. It is not available in IE.
|
||||
*
|
||||
* if (!window.btoa) window.btoa = base64.encode
|
||||
* if (!window.atob) window.atob = base64.decode
|
||||
*
|
||||
* The original spec's for atob/btoa are a bit lacking
|
||||
* https://developer.mozilla.org/en/DOM/window.atob
|
||||
* https://developer.mozilla.org/en/DOM/window.btoa
|
||||
*
|
||||
* window.btoa and base64.encode takes a string where charCodeAt is [0,255]
|
||||
* If any character is not [0,255], then an DOMException(5) is thrown.
|
||||
*
|
||||
* window.atob and base64.decode take a base64-encoded string
|
||||
* If the input length is not a multiple of 4, or contains invalid characters
|
||||
* then an DOMException(5) is thrown.
|
||||
*/
|
||||
var base64 = {};
|
||||
base64.PADCHAR = '=';
|
||||
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
base64.makeDOMException = function() {
|
||||
// sadly in FF,Safari,Chrome you can't make a DOMException
|
||||
var e, tmp;
|
||||
|
||||
try {
|
||||
return new DOMException(DOMException.INVALID_CHARACTER_ERR);
|
||||
} catch (tmp) {
|
||||
// not available, just passback a duck-typed equiv
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
|
||||
var ex = new Error("DOM Exception 5");
|
||||
|
||||
// ex.number and ex.description is IE-specific.
|
||||
ex.code = ex.number = 5;
|
||||
ex.name = ex.description = "INVALID_CHARACTER_ERR";
|
||||
|
||||
// Safari/Chrome output format
|
||||
ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
base64.getbyte64 = function(s,i) {
|
||||
// This is oddly fast, except on Chrome/V8.
|
||||
// Minimal or no improvement in performance by using a
|
||||
// object with properties mapping chars to value (eg. 'A': 0)
|
||||
var idx = base64.ALPHA.indexOf(s.charAt(i));
|
||||
if (idx === -1) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
base64.decode = function(s) {
|
||||
// convert to string
|
||||
s = '' + s;
|
||||
var getbyte64 = base64.getbyte64;
|
||||
var pads, i, b10;
|
||||
var imax = s.length
|
||||
if (imax === 0) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (imax % 4 !== 0) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
|
||||
pads = 0
|
||||
if (s.charAt(imax - 1) === base64.PADCHAR) {
|
||||
pads = 1;
|
||||
if (s.charAt(imax - 2) === base64.PADCHAR) {
|
||||
pads = 2;
|
||||
}
|
||||
// either way, we want to ignore this last block
|
||||
imax -= 4;
|
||||
}
|
||||
|
||||
var x = [];
|
||||
for (i = 0; i < imax; i += 4) {
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
|
||||
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
|
||||
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
|
||||
}
|
||||
|
||||
switch (pads) {
|
||||
case 1:
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
|
||||
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
|
||||
break;
|
||||
case 2:
|
||||
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
|
||||
x.push(String.fromCharCode(b10 >> 16));
|
||||
break;
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
base64.getbyte = function(s,i) {
|
||||
var x = s.charCodeAt(i);
|
||||
if (x > 255) {
|
||||
throw base64.makeDOMException();
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
base64.encode = function(s) {
|
||||
if (arguments.length !== 1) {
|
||||
throw new SyntaxError("Not enough arguments");
|
||||
}
|
||||
var padchar = base64.PADCHAR;
|
||||
var alpha = base64.ALPHA;
|
||||
var getbyte = base64.getbyte;
|
||||
|
||||
var i, b10;
|
||||
var x = [];
|
||||
|
||||
// convert to string
|
||||
s = '' + s;
|
||||
|
||||
var imax = s.length - s.length % 3;
|
||||
|
||||
if (s.length === 0) {
|
||||
return s;
|
||||
}
|
||||
for (i = 0; i < imax; i += 3) {
|
||||
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
|
||||
x.push(alpha.charAt(b10 >> 18));
|
||||
x.push(alpha.charAt((b10 >> 12) & 0x3F));
|
||||
x.push(alpha.charAt((b10 >> 6) & 0x3f));
|
||||
x.push(alpha.charAt(b10 & 0x3f));
|
||||
}
|
||||
switch (s.length - imax) {
|
||||
case 1:
|
||||
b10 = getbyte(s,i) << 16;
|
||||
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
||||
padchar + padchar);
|
||||
break;
|
||||
case 2:
|
||||
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
|
||||
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
||||
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
|
||||
break;
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
if (!window.btoa) window.btoa = base64.encode
|
||||
if (!window.atob) window.atob = base64.decode
|
||||
161
packages/wekan-cfs-base-package/tests/common-tests.js
Normal file
161
packages/wekan-cfs-base-package/tests/common-tests.js
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
function equals(a, b) {
|
||||
return EJSON.stringify(a) === EJSON.stringify(b);
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-base-package - test environment', function(test) {
|
||||
test.isTrue(typeof FS !== 'undefined',
|
||||
'FS scope not declared');
|
||||
|
||||
test.isTrue(typeof FS.Store !== 'undefined',
|
||||
'FS scope "FS.Store" not declared');
|
||||
|
||||
test.isTrue(typeof FS.AccessPoint !== 'undefined',
|
||||
'FS scope "FS.AccessPoint" not declared');
|
||||
|
||||
test.isTrue(typeof FS.Utility !== 'undefined',
|
||||
'FS scope "FS.Utility" not declared');
|
||||
|
||||
test.isTrue(typeof FS._collections !== 'undefined',
|
||||
'FS scope "FS._collections" not declared');
|
||||
|
||||
test.isTrue(typeof _Utility !== 'undefined',
|
||||
'_Utility test scope not declared');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - _Utility.defaultZero', function(test) {
|
||||
test.equal(_Utility.defaultZero(), 0, 'Failes to return 0 when (undefined)');
|
||||
test.equal(_Utility.defaultZero(undefined), 0, 'Failes to return 0 when undefined');
|
||||
test.equal(_Utility.defaultZero(null), 0, 'Failes to return 0 when null');
|
||||
test.equal(_Utility.defaultZero(false), 0, 'Failes to return 0 when false');
|
||||
test.equal(_Utility.defaultZero(0), 0, 'Failes to return 0 when 0');
|
||||
test.equal(_Utility.defaultZero(-1), -1, 'Failes to return -1');
|
||||
test.equal(_Utility.defaultZero(1), 1, 'Failes to return 1');
|
||||
test.equal(_Utility.defaultZero(-0.1), -0.1, 'Failes to return -0.1');
|
||||
test.equal(_Utility.defaultZero(0.1), 0.1, 'Failes to return 0.1');
|
||||
test.equal(_Utility.defaultZero(''), 0, 'Failes to return ""');
|
||||
test.equal(_Utility.defaultZero({}), NaN, 'Failes to return NaN when object');
|
||||
test.equal(_Utility.defaultZero("dfdsfs"), NaN, 'Failes to return NaN when string');
|
||||
test.equal(_Utility.defaultZero("1"), 1, 'Failes to return 1 when string "1"');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.cloneFileRecord', function(test) {
|
||||
// Given an object with any props, should filter out 'collectionName',
|
||||
// 'collection', 'data', and 'createdByTransform'
|
||||
var result = FS.Utility.cloneFileRecord({a: 1, b: {c: 1}, d: [1, 2], collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
|
||||
test.equal(result, {a: 1, b: {c: 1}, d: [1, 2]});
|
||||
|
||||
// Given an FS.File instance, should filter out 'collectionName',
|
||||
// 'collection', 'data', and 'createdByTransform' and return a plain Object
|
||||
var fileObj = new FS.File({a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100, collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
|
||||
test.isTrue(fileObj instanceof FS.File);
|
||||
var result = FS.Utility.cloneFileRecord(fileObj);
|
||||
test.isFalse(result instanceof FS.File);
|
||||
test.isTrue(equals(result, {a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100}));
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.defaultCallback', function(test) {
|
||||
// should throw an error passed in, but not a Meteor.Error
|
||||
test.throws(function () {
|
||||
var cb = FS.Utility.defaultCallback;
|
||||
cb(new Error('test'));
|
||||
});
|
||||
|
||||
var cb2 = FS.Utility.defaultCallback;
|
||||
test.isUndefined(cb2(new Meteor.Error('test')));
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.handleError', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.binaryToBuffer', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.bufferToBinary', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.connectionLogin', function(test) {
|
||||
test.isTrue(true);
|
||||
// TODO
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.getFileName', function(test) {
|
||||
|
||||
function t(input, expected) {
|
||||
var ext = FS.Utility.getFileName(input);
|
||||
test.equal(ext, expected, 'Got incorrect filename');
|
||||
}
|
||||
|
||||
t('bar.png', 'bar.png');
|
||||
t('foo/bar.png', 'bar.png');
|
||||
t('/foo/foo/bar.png', 'bar.png');
|
||||
t('http://foobar.com/file.png', 'file.png');
|
||||
t('http://foobar.com/file', 'file');
|
||||
t('http://foobar.com/file.png?a=b', 'file.png');
|
||||
t('http://foobar.com/.file?a=b', '.file');
|
||||
t('file', 'file');
|
||||
t('.file', '.file');
|
||||
t('foo/.file', '.file');
|
||||
t('/foo/foo/.file', '.file');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.getFileExtension', function(test) {
|
||||
|
||||
function t(input, expected) {
|
||||
var ext = FS.Utility.getFileExtension(input);
|
||||
test.equal(ext, expected, 'Got incorrect extension');
|
||||
}
|
||||
|
||||
t('bar.png', 'png');
|
||||
t('foo/bar.png', 'png');
|
||||
t('/foo/foo/bar.png', 'png');
|
||||
t('http://foobar.com/file.png', 'png');
|
||||
t('http://foobar.com/file', '');
|
||||
t('http://foobar.com/file.png?a=b', 'png');
|
||||
t('http://foobar.com/file?a=b', '');
|
||||
t('file', '');
|
||||
t('.file', '');
|
||||
t('foo/.file', '');
|
||||
t('/foo/foo/.file', '');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-base-package - FS.Utility.setFileExtension', function(test) {
|
||||
|
||||
function t(name, ext, expected) {
|
||||
var newName = FS.Utility.setFileExtension(name, ext);
|
||||
test.equal(newName, expected, 'Extension was not set correctly');
|
||||
}
|
||||
|
||||
t('bar.png', 'jpeg', 'bar.jpeg');
|
||||
t('bar', 'jpeg', 'bar.jpeg');
|
||||
t('.bar', 'jpeg', '.bar.jpeg');
|
||||
t('', 'jpeg', '');
|
||||
t(null, 'jpeg', null);
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//Tinytest.add('', function(test) {});
|
||||
//Tinytest.addAsync('', function(test, onComplete) {});
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
5
packages/wekan-cfs-collection-filters/.travis.yml
Normal file
5
packages/wekan-cfs-collection-filters/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
20
packages/wekan-cfs-collection-filters/LICENSE.md
Normal file
20
packages/wekan-cfs-collection-filters/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
8
packages/wekan-cfs-collection-filters/README.md
Normal file
8
packages/wekan-cfs-collection-filters/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-collection-filters
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
||||
44
packages/wekan-cfs-collection-filters/api.md
Normal file
44
packages/wekan-cfs-collection-filters/api.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
## cfs-collection-filters Public API ##
|
||||
|
||||
CollectionFS, adds FS.Collection filters
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.filters"></a>*fsCollection*.filters(filters) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __filters__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filters__ *{Object}*
|
||||
|
||||
File filters for this collection.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allowsFile"></a>*fsCollection*.allowsFile() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allowsFile__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the collection allows this file.
|
||||
|
||||
|
||||
Checks based on any filters defined on the collection. If the
|
||||
file is not valid according to the filters, this method returns false
|
||||
and also calls the filter `onInvalid` method defined for the
|
||||
collection, passing it an English error string that explains why it
|
||||
failed.
|
||||
|
||||
> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108)
|
||||
|
||||
|
||||
191
packages/wekan-cfs-collection-filters/filters.js
Normal file
191
packages/wekan-cfs-collection-filters/filters.js
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* @method FS.Collection.prototype.filters
|
||||
* @public
|
||||
* @param {Object} filters - File filters for this collection.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.Collection.prototype.filters = function fsColFilters(filters) {
|
||||
var self = this;
|
||||
|
||||
// Check filter option values and normalize them for quicker checking later
|
||||
if (filters) {
|
||||
// check/adjust allow/deny
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type]) {
|
||||
filters[type] = {};
|
||||
} else if (typeof filters[type] !== "object") {
|
||||
throw new Error(type + ' filter must be an object');
|
||||
}
|
||||
});
|
||||
|
||||
// check/adjust maxSize
|
||||
if (typeof filters.maxSize === "undefined") {
|
||||
filters.maxSize = null;
|
||||
} else if (filters.maxSize && typeof filters.maxSize !== "number") {
|
||||
throw new Error('maxSize filter must be an number');
|
||||
}
|
||||
|
||||
// check/adjust extensions
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type].extensions) {
|
||||
filters[type].extensions = [];
|
||||
} else if (!FS.Utility.isArray(filters[type].extensions)) {
|
||||
throw new Error(type + '.extensions filter must be an array of extensions');
|
||||
} else {
|
||||
//convert all to lowercase
|
||||
for (var i = 0, ln = filters[type].extensions.length; i < ln; i++) {
|
||||
filters[type].extensions[i] = filters[type].extensions[i].toLowerCase();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// check/adjust content types
|
||||
FS.Utility.each(['allow', 'deny'], function (type) {
|
||||
if (!filters[type].contentTypes) {
|
||||
filters[type].contentTypes = [];
|
||||
} else if (!FS.Utility.isArray(filters[type].contentTypes)) {
|
||||
throw new Error(type + '.contentTypes filter must be an array of content types');
|
||||
}
|
||||
});
|
||||
|
||||
self.options.filter = filters;
|
||||
}
|
||||
|
||||
// Define deny functions to enforce file filters on the server
|
||||
// for inserts and updates that initiate from untrusted code.
|
||||
self.files.deny({
|
||||
insert: function(userId, fsFile) {
|
||||
return !self.allowsFile(fsFile);
|
||||
},
|
||||
update: function(userId, fsFile, fields, modifier) {
|
||||
// TODO will need some kind of additional security here:
|
||||
// Don't allow them to change the type, size, name, and
|
||||
// anything else that would be security or data integrity issue.
|
||||
// Such security should probably be added by cfs-collection package, not here.
|
||||
return !self.allowsFile(fsFile);
|
||||
},
|
||||
fetch: []
|
||||
});
|
||||
|
||||
// If insecure package is in use, we need to add allow rules that return
|
||||
// true. Otherwise, it would seemingly turn off insecure mode.
|
||||
if (Package && Package.insecure) {
|
||||
self.allow({
|
||||
insert: function() {
|
||||
return true;
|
||||
},
|
||||
update: function() {
|
||||
return true;
|
||||
},
|
||||
remove: function() {
|
||||
return true;
|
||||
},
|
||||
download: function() {
|
||||
return true;
|
||||
},
|
||||
fetch: [],
|
||||
transform: null
|
||||
});
|
||||
}
|
||||
// If insecure package is NOT in use, then adding the deny function
|
||||
// does not have any effect on the main app's security paradigm. The
|
||||
// user will still be required to add at least one allow function of her
|
||||
// own for each operation for this collection. And the user may still add
|
||||
// additional deny functions, but does not have to.
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.Collection.prototype.allowsFile Does the collection allow the specified file?
|
||||
* @public
|
||||
* @returns {boolean} True if the collection allows this file.
|
||||
*
|
||||
* Checks based on any filters defined on the collection. If the
|
||||
* file is not valid according to the filters, this method returns false
|
||||
* and also calls the filter `onInvalid` method defined for the
|
||||
* collection, passing it an English error string that explains why it
|
||||
* failed.
|
||||
*/
|
||||
FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
|
||||
var self = this;
|
||||
|
||||
// Get filters
|
||||
var filter = self.options.filter;
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
var saveAllFileExtensions = (filter.allow.extensions.length === 0);
|
||||
var saveAllContentTypes = (filter.allow.contentTypes.length === 0);
|
||||
|
||||
// Get info about the file
|
||||
var filename = fileObj.name();
|
||||
var contentType = fileObj.type();
|
||||
if (!saveAllContentTypes && !contentType) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " has an unknown content type");
|
||||
return false;
|
||||
}
|
||||
var fileSize = fileObj.size();
|
||||
if (!fileSize || isNaN(fileSize)) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " has an unknown file size");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do extension checks only if we have a filename
|
||||
if (filename) {
|
||||
var ext = fileObj.getExtension();
|
||||
if (!((saveAllFileExtensions ||
|
||||
FS.Utility.indexOf(filter.allow.extensions, ext) !== -1) &&
|
||||
FS.Utility.indexOf(filter.deny.extensions, ext) === -1)) {
|
||||
filter.onInvalid && filter.onInvalid(filename + ' has the extension "' + ext + '", which is not allowed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Do content type checks
|
||||
if (!((saveAllContentTypes ||
|
||||
contentTypeInList(filter.allow.contentTypes, contentType)) &&
|
||||
!contentTypeInList(filter.deny.contentTypes, contentType))) {
|
||||
filter.onInvalid && filter.onInvalid(filename + ' is of the type "' + contentType + '", which is not allowed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do max size check
|
||||
if (typeof filter.maxSize === "number" && fileSize > filter.maxSize) {
|
||||
filter.onInvalid && filter.onInvalid(filename + " is too big");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method contentTypeInList Is the content type string in the list?
|
||||
* @private
|
||||
* @param {String[]} list - Array of content types
|
||||
* @param {String} contentType - The content type
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* Returns true if the content type is in the list, or if it matches
|
||||
* one of the special types in the list, e.g., "image/*".
|
||||
*/
|
||||
function contentTypeInList(list, contentType) {
|
||||
var listType, found = false;
|
||||
for (var i = 0, ln = list.length; i < ln; i++) {
|
||||
listType = list[i];
|
||||
if (listType === contentType) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "image/*" && contentType.indexOf("image/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "audio/*" && contentType.indexOf("audio/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (listType === "video/*" && contentType.indexOf("video/") === 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
72
packages/wekan-cfs-collection-filters/internal.api.md
Normal file
72
packages/wekan-cfs-collection-filters/internal.api.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["filters.js"](filters.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Collection.prototype.filters"></a>*fsCollection*.filters(filters) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __filters__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filters__ *{Object}*
|
||||
|
||||
File filters for this collection.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allowsFile"></a>*fsCollection*.allowsFile() <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allowsFile__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Returns__ *{boolean}*
|
||||
True if the collection allows this file.
|
||||
|
||||
|
||||
Checks based on any filters defined on the collection. If the
|
||||
file is not valid according to the filters, this method returns false
|
||||
and also calls the filter `onInvalid` method defined for the
|
||||
collection, passing it an English error string that explains why it
|
||||
failed.
|
||||
|
||||
> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="contentTypeInList"></a>contentTypeInList(list, contentType) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __list__ *{[String[]](#String[])}*
|
||||
|
||||
Array of content types
|
||||
|
||||
* __contentType__ *{String}*
|
||||
|
||||
The content type
|
||||
|
||||
|
||||
__Returns__ *{Boolean}*
|
||||
|
||||
|
||||
Returns true if the content type is in the list, or if it matches
|
||||
one of the special types in the list, e.g., "image/*".
|
||||
|
||||
> ```function contentTypeInList(list, contentType) { ...``` [filters.js:169](filters.js#L169)
|
||||
|
||||
|
||||
29
packages/wekan-cfs-collection-filters/package.js
Normal file
29
packages/wekan-cfs-collection-filters/package.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-collection-filters.git',
|
||||
name: 'wekan-cfs-collection-filters',
|
||||
version: '0.2.4',
|
||||
summary: 'CollectionFS, adds FS.Collection filters'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-collection@0.5.4']);
|
||||
|
||||
api.addFiles([
|
||||
'filters.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'filters.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
// Package.on_test(function (api) {
|
||||
// api.use('collectionfs');
|
||||
// api.use('test-helpers', 'server');
|
||||
// api.use(['tinytest']);
|
||||
|
||||
// api.addFiles('tests/server-tests.js', 'server');
|
||||
// api.addFiles('tests/client-tests.js', 'client');
|
||||
// });
|
||||
27
packages/wekan-cfs-collection-filters/tests/client-tests.js
Normal file
27
packages/wekan-cfs-collection-filters/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection-filters - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
27
packages/wekan-cfs-collection-filters/tests/server-tests.js
Normal file
27
packages/wekan-cfs-collection-filters/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection-filters - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
5
packages/wekan-cfs-collection/.travis.yml
Normal file
5
packages/wekan-cfs-collection/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
727
packages/wekan-cfs-collection/CHANGELOG.md
Normal file
727
packages/wekan-cfs-collection/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
# Changelog
|
||||
|
||||
## vCurrent
|
||||
## [v0.5.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.3)
|
||||
#### 20/12/14 by Morten Henriksen
|
||||
- add changelog
|
||||
|
||||
- Bump to version 0.5.3
|
||||
|
||||
## [v0.5.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.2)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- Bump to version 0.5.2
|
||||
|
||||
## [v0.5.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.1)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Bump to version 0.5.1
|
||||
|
||||
## [v0.5.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.0)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- mbr update versions and fix warnings
|
||||
|
||||
- update pkg, dependencies, etc.
|
||||
|
||||
- Fix #483
|
||||
|
||||
- *Merged pull-request:* "mrt graphicsmagick instructions to meteor 0.9+" [#442](https://github.com/zcfs/Meteor-CollectionFS/issues/442) ([yogiben](https://github.com/yogiben))
|
||||
|
||||
- mrt graphicsmagick instructions to meteor 0.9+
|
||||
|
||||
- mention underlying collection
|
||||
|
||||
- *Merged pull-request:* "Added server-side Buffer example to README.md" [#405](https://github.com/zcfs/Meteor-CollectionFS/issues/405) ([abuddenb](https://github.com/abuddenb))
|
||||
|
||||
- change prop names
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- add "Display an Uploaded Image" example
|
||||
|
||||
- addition to steps
|
||||
|
||||
- correct 0.9.0 steps
|
||||
|
||||
- Merge branch 'devel' of github.com:abuddenb/Meteor-CollectionFS into devel
|
||||
|
||||
- Added server-side Buffer example to README.md
|
||||
|
||||
Patches by GitHub users [@yogiben](https://github.com/yogiben), [@abuddenb](https://github.com/abuddenb).
|
||||
|
||||
## [v0.4.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.9)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
- add 0.9.0 instructions
|
||||
|
||||
- change package name to lowercase
|
||||
|
||||
- Added server-side Buffer example to README.md
|
||||
|
||||
- *Merged pull-request:* "Updated README.md to reflect that FS.File - Objects can't be stored in t..." [#395](https://github.com/zcfs/Meteor-CollectionFS/issues/395) ([DanielDornhardt](https://github.com/DanielDornhardt))
|
||||
|
||||
- Updated README.md to reflect that FS.File - Objects can't be stored in the Server MongoDB at the moment.
|
||||
|
||||
- document key
|
||||
|
||||
- update the descriptions of collections
|
||||
|
||||
- document beforeWrite
|
||||
|
||||
- add download button example
|
||||
|
||||
- *Merged pull-request:* "Update README.md" [#354](https://github.com/zcfs/Meteor-CollectionFS/issues/354) ([karabijavad](https://github.com/karabijavad))
|
||||
|
||||
- should say to use cfs-graphicsmagick pkg
|
||||
|
||||
- fix typo
|
||||
|
||||
- *Merged pull-request:* "Added "Storing FS.File references in your objects" chapter to README" [#311](https://github.com/zcfs/Meteor-CollectionFS/issues/311) ([Sanjo](https://github.com/Sanjo))
|
||||
|
||||
- Added "Storing FS.File references in your objects" chapter to README
|
||||
|
||||
- a few more API updates
|
||||
|
||||
- updates for changed FS.File API
|
||||
|
||||
- additions and clarifications
|
||||
|
||||
- *Merged pull-request:* "Custom Metadata misleading example" [#282](https://github.com/zcfs/Meteor-CollectionFS/issues/282) ([czeslaaw](https://github.com/czeslaaw))
|
||||
|
||||
- Custom Metadata misleading example
|
||||
|
||||
Patches by GitHub users [@DanielDornhardt](https://github.com/DanielDornhardt), [@karabijavad](https://github.com/karabijavad), [@Sanjo](https://github.com/Sanjo), [@czeslaaw](https://github.com/czeslaaw).
|
||||
|
||||
## [v0.4.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.8)
|
||||
#### 09/04/14 by Eric Dobbertin
|
||||
- Add cfs-collection-filters pkg by default
|
||||
|
||||
- Use FS.Utility.setFileExtension in example
|
||||
|
||||
## [v0.4.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.7)
|
||||
#### 05/04/14 by Morten Henriksen
|
||||
- corrections, and move image examples here so we can deprecate cfs-imagemagick
|
||||
|
||||
## [v0.4.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.6)
|
||||
#### 02/04/14 by Morten Henriksen
|
||||
- *Fixed bug:* "Documentation Issues" [#241](https://github.com/zcfs/Meteor-CollectionFS/issues/241)
|
||||
|
||||
- point to cfs-graphicsmagick readme for transform examples
|
||||
|
||||
## [v0.4.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.5)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
- Add yet a note about mrt and collectionFS in the readme
|
||||
|
||||
## [v0.4.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.4)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- attempt to get everything up to date
|
||||
|
||||
- Add notice about mrt having troubles figuring out deps
|
||||
|
||||
- *Merged pull-request:* "Removed redundant installation instructions." [#212](https://github.com/zcfs/Meteor-CollectionFS/issues/212) ([lleonard188](https://github.com/lleonard188))
|
||||
|
||||
- up to date
|
||||
|
||||
- read me update
|
||||
|
||||
- add test note
|
||||
|
||||
Patches by GitHub user [@lleonard188](https://github.com/lleonard188).
|
||||
|
||||
## [v0.4.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.3)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- use collectionFS travis version force update
|
||||
|
||||
## [v0.4.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.2)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- change deps
|
||||
|
||||
- Add read me about transformWrite
|
||||
|
||||
## [v0.4.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.1)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- update reference to devel branch
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel
|
||||
|
||||
- don't need to install transfer
|
||||
|
||||
- mark deprecated api docs
|
||||
|
||||
- test strikeout md
|
||||
|
||||
- no need to imply cfs-transfer since it doesn't do anything at the moment
|
||||
|
||||
- Remove ejson-file imply
|
||||
|
||||
## [v0.4.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.0)
|
||||
#### 06/03/14 by Eric Dobbertin
|
||||
- add section for documenting example code for common tasks, and add some of the examples; @raix feel free to add more
|
||||
|
||||
- add http upload package to smart.json
|
||||
|
||||
- Merge origin/devel into devel
|
||||
|
||||
- need to imply the http upload package because it's the default now
|
||||
|
||||
- update HTTP access point override docs
|
||||
|
||||
- update docs to use `devel` branch
|
||||
|
||||
- list component packages in smart.json
|
||||
|
||||
- Merge origin/devel into devel-merge
|
||||
|
||||
- add the ejson-file and correct doc
|
||||
|
||||
- refactor everything into packages
|
||||
|
||||
## [devel-merge-old] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-merge-old)
|
||||
#### 12/02/14 by Eric Dobbertin
|
||||
- update to latest FileSaver.js
|
||||
|
||||
- one of many refactores
|
||||
|
||||
- add optimization section
|
||||
|
||||
- add section explaining client vs server insert
|
||||
|
||||
- prevent autopublish for the SA collections
|
||||
|
||||
- remove arg that isn't used or passed
|
||||
|
||||
- changes to support client SA, plus clean/reorg SA code to have less duplication
|
||||
|
||||
- merge the concepts of "store" and "copy", update docs, switch to FS.Store namespace
|
||||
|
||||
- use wait:true to avoid Meteor issue
|
||||
|
||||
- extend allow when insecure package is installed
|
||||
|
||||
- document and improve code flow a bit
|
||||
|
||||
- faster to use onResultReceived callback
|
||||
|
||||
- use onload instead of onloadend because we want only successful loads
|
||||
|
||||
- remove some console logging
|
||||
|
||||
- fix issue where stuff wasn't uploading because we weren't calling getFileRecord() in the access point methods
|
||||
|
||||
- make allowed file extension checks not be case sensitive
|
||||
|
||||
- remove DDP "/del" access point since it's not used or necessary
|
||||
|
||||
- Remove collection-hooks dependency; use deny instead. Also some cleanup and code docs
|
||||
|
||||
- replace `mmmagic` dependency with a `mime` dependency; hopefully fixes issues we've seen with meteor deploy
|
||||
|
||||
- refactor to expose saveCopy to API; then change fileworker observes to be per-copy, calling saveCopy and allowing better control of which copies to create at which times
|
||||
|
||||
- use observes for all store saving and temp store deleting; add/adjust some api doc comments (didn't regenerate yet)
|
||||
|
||||
- improve fileIsAllowed check order and messages
|
||||
|
||||
- Add check for the Accounts package
|
||||
|
||||
- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120)
|
||||
|
||||
- always append access token to url when a user is logged in; makes usage simpler to do opt-out rather than opt-in
|
||||
|
||||
- Don't queue ddp method calls
|
||||
|
||||
- Merge changes
|
||||
|
||||
- Optimize buffer handling
|
||||
|
||||
- Switch to MicroQueue
|
||||
|
||||
- improve memory management, attempt client side resume implementation, other minor fixes
|
||||
|
||||
- fix several issues, make downloading work, improve a few bits of code
|
||||
|
||||
- do the isImage test correctly; add some API docs
|
||||
|
||||
- isEmpty will be true for null or undefined
|
||||
|
||||
- rewrite getExtension so it works for unmounted files, too
|
||||
|
||||
- add strong reactive-list dependency for powerqueue
|
||||
|
||||
- minor fixes and code improvements; track uploading files by both collection and ID since one queue handles all collections
|
||||
|
||||
- Base the upload queue on PowerQueue sub queues
|
||||
|
||||
- Add sa note
|
||||
|
||||
- limit use of db
|
||||
|
||||
- Refactor and documentation
|
||||
|
||||
- use mmmagic 0.3.5
|
||||
|
||||
- *Fixed bug:* "Option to set Cache-Control and Max-Age" [#117](https://github.com/zcfs/Meteor-CollectionFS/issues/117)
|
||||
|
||||
- fix stuff that's broken
|
||||
|
||||
## [v0.3.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.7)
|
||||
#### 08/01/14 by Morten Henriksen
|
||||
- *Fixed bug:* "How to store files on the server side?" [#29](https://github.com/zcfs/Meteor-CollectionFS/issues/29)
|
||||
|
||||
- rework ejson and remove fsFile.reload
|
||||
|
||||
- explain complete upload process in ADVANCED docs
|
||||
|
||||
- Adds join to smart.json (its a weak dependency #119)
|
||||
|
||||
- link to api docs for acceptDropsOn
|
||||
|
||||
- typo/formatting fixes
|
||||
|
||||
- docs + insert callback returns `FS.File` instead of `id`
|
||||
|
||||
- Add docs and `FS.File.fetch`
|
||||
|
||||
- Add support for `Join`
|
||||
|
||||
- end with line
|
||||
|
||||
- smaller headlines in docs
|
||||
|
||||
- init api docs
|
||||
|
||||
- Refactor and documentation
|
||||
|
||||
- FS.Collection.insert should return the `FS.File` object
|
||||
|
||||
- remove some comments and such
|
||||
|
||||
- document custom connections
|
||||
|
||||
- add livedata ref to access DDP obj
|
||||
|
||||
- use separate ddp connection with option to pass in custom
|
||||
|
||||
- public folder and gm/im
|
||||
|
||||
- fix null options
|
||||
|
||||
- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120)
|
||||
|
||||
- make useHTTP true by default
|
||||
|
||||
- skip auth checks if Package.insecure
|
||||
|
||||
- remove `callback` arg from fsFile.get since it's not used or necessary; fix partial gets such that they actually use getBytes, greatly speeding up downloads of large files
|
||||
|
||||
- reorg code and speed up downloads
|
||||
|
||||
- split TransferQueue into DownloadTransferQueue and UploadTransferQueue
|
||||
|
||||
- improvements to make use of new PowerQueue features
|
||||
|
||||
- fix issue with previous commit
|
||||
|
||||
- add accessPoints option
|
||||
|
||||
- Pull out temporary chunk code into a separate tempStore.js file, within a TempStore object. This makes it easier to maintain. Also updated the file worker code to correctly find temporary chunks that can be removed and delete those files.
|
||||
|
||||
- add security section
|
||||
|
||||
- Internally, change all "master" stuff to be the same as "copies". External API is still the same, but master options are copied to a special copy named "_master" so that all the other code can be cleaner. This may be a step toward being able to blur or eliminate the master/copy distinction, although there are still some benefits to having a master.
|
||||
|
||||
- Add instructions for installing for testing
|
||||
|
||||
- refactor code to be a bit cleaner
|
||||
|
||||
- add functions for getting an FS.File or setting FS.File data from a URL on the server
|
||||
|
||||
- clean up and improve some transfer code
|
||||
|
||||
- update console log message to be more correct
|
||||
|
||||
- ensure that fsFile.bytesUploaded is always set correctly
|
||||
|
||||
- fix some issues with recent commits
|
||||
|
||||
- *Merged pull-request:* "Updates the filter example area so that it works" [#109](https://github.com/zcfs/Meteor-CollectionFS/issues/109) ([cramhead](https://github.com/cramhead))
|
||||
|
||||
- add .npm to gitignore
|
||||
|
||||
- Remove .npm folder
|
||||
|
||||
- Clean clone just a bit
|
||||
|
||||
- Refactor fsCollection and argParser
|
||||
|
||||
- Comment on filter options
|
||||
|
||||
- Add download url
|
||||
|
||||
- refactor access point
|
||||
|
||||
- add put/get/del security based on allow/deny functions
|
||||
|
||||
- Updates the filter example area so that it works
|
||||
|
||||
- update for API change
|
||||
|
||||
- more client-side speed improvements and allow passing File/Blob to FS.File constructor again
|
||||
|
||||
- fix data mixup
|
||||
|
||||
- fix upload slowness and blocking
|
||||
|
||||
- change API to adjust issue with data loading callbacks
|
||||
|
||||
- revise FS.File API where data handling is concerned; fix some issues with callback handling in client-side methods; upshot should be faster, smoother uploads and downloads
|
||||
|
||||
- change names and put everything in exported FS namespace
|
||||
|
||||
- fix get/download of copies
|
||||
|
||||
- *Fixed bug:* "Files after certain size aren't saved properly. " [#104](https://github.com/zcfs/Meteor-CollectionFS/issues/104)
|
||||
|
||||
- add fileobject metadata and acceptDropsOn
|
||||
|
||||
- add some methods to load FO data from URL
|
||||
|
||||
- Correct allow/deny examples
|
||||
|
||||
- call put callback correctly
|
||||
|
||||
- add correct temporary installation instructions
|
||||
|
||||
- use correct filename when saving download
|
||||
|
||||
- filtering fixes
|
||||
|
||||
- removed some unused stuff
|
||||
|
||||
- adjust some comments
|
||||
|
||||
- switch api.remove to api.del for consistency with the other methods
|
||||
|
||||
- add hasCopy method
|
||||
|
||||
- new api; tons of changes
|
||||
|
||||
- use generic queue for server file handling
|
||||
|
||||
- update progress in the correct place
|
||||
|
||||
- minor changes to comments
|
||||
|
||||
- fix gridfs get method
|
||||
|
||||
- incorporate #82
|
||||
|
||||
- make filtering work (added collection-hooks dependency for core package)
|
||||
|
||||
- change UploadsCollection to CollectionFS; change former CollectionFS to GridFS and don't export it (used only by the gridfs storage adaptor); clean up some other areas and update readmes
|
||||
|
||||
- *Fixed bug:* "retrieveBlob failure" [#93](https://github.com/zcfs/Meteor-CollectionFS/issues/93)
|
||||
|
||||
- implement http methods URLs
|
||||
|
||||
- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS.git into devel
|
||||
|
||||
- significant revisions to move downloading support to the UploadsCollection and make collectionFS/gridFS a pure storage adaptor
|
||||
|
||||
- Merge branch 'pr/94' into devel
|
||||
|
||||
- split and revise readmes
|
||||
|
||||
- rename packages and organize package.js
|
||||
|
||||
- error handling improvements
|
||||
|
||||
- better failure handling for removeCopy
|
||||
|
||||
- handlebars helper to display blob image in CFS package
|
||||
|
||||
- remove all encoding info
|
||||
|
||||
- refactor to fix multiple-file simultaneous uploads
|
||||
|
||||
- don't use _id in filesystem destination since it's not set anyway
|
||||
|
||||
- remove FileObject.file and instead save file as Blob (.blob) when FileObject.fromFile is called
|
||||
|
||||
- revise API a bit
|
||||
|
||||
- stop using strings and encoding and pass everything as Uint8Array (fixes downloading corruption)
|
||||
|
||||
- only attempt to delete file if it exists
|
||||
|
||||
- remove unused file
|
||||
|
||||
- complete refactoring; temporary for testing/tweaking and then will split into multiple packages
|
||||
|
||||
- *Merged pull-request:* "Implemented max parallel transfers" [#62](https://github.com/zcfs/Meteor-CollectionFS/issues/62) ([floo51](https://github.com/floo51))
|
||||
|
||||
Patches by GitHub users [@cramhead](https://github.com/cramhead), [@floo51](https://github.com/floo51).
|
||||
|
||||
## [v0.3.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.6)
|
||||
#### 10/10/13 by Morten Henriksen
|
||||
- Edit ideas about storage adapters and filehandlers
|
||||
|
||||
- Add MIT License
|
||||
|
||||
- Add paypal and weak deps
|
||||
|
||||
- Added the org. filemanager demo/example
|
||||
|
||||
## [v0.3.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.5)
|
||||
#### 20/09/13 by Morten Henriksen
|
||||
## [v0.3.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.4)
|
||||
#### 20/09/13 by Morten Henriksen
|
||||
- Extract the examples from collectionFS
|
||||
|
||||
- Added examples from @mxab
|
||||
|
||||
## [v0.3.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.3)
|
||||
#### 18/09/13 by Morten Henriksen
|
||||
- added travis badge
|
||||
|
||||
- Added travis badge
|
||||
|
||||
- Added basic environment
|
||||
|
||||
- Added metadata getter to docs
|
||||
|
||||
## [v0.3.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.2)
|
||||
#### 17/09/13 by Morten Henriksen
|
||||
## [v0.3.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.1)
|
||||
#### 16/09/13 by Morten Henriksen
|
||||
- Added some details about http.publishing of collections
|
||||
|
||||
- added check in filehandlers
|
||||
|
||||
- Updated som docs
|
||||
|
||||
- js hint added scope
|
||||
|
||||
- jshint clean up
|
||||
|
||||
- *Fixed bug:* "Exception from setTimeout callback: { stack: [Getter] }" [#64](https://github.com/zcfs/Meteor-CollectionFS/issues/64)
|
||||
|
||||
- Merge branch 'devel'
|
||||
|
||||
- Bump version to preview of 0.3.0
|
||||
|
||||
- Implemented max parallel transfers
|
||||
|
||||
- Revert "began refractoring"
|
||||
|
||||
- Revert "Run jshint through files"
|
||||
|
||||
- Revert "Preparing file object methods for better api"
|
||||
|
||||
- Revert "added fileobject files to package"
|
||||
|
||||
- added fileobject files to package
|
||||
|
||||
- Preparing file object methods for better api
|
||||
|
||||
- Run jshint through files
|
||||
|
||||
- *Merged pull-request:* "Improvements and Fixes to built-in helpers, storeFiles, and acceptDropsOn" [#51](https://github.com/zcfs/Meteor-CollectionFS/issues/51) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Add generic events system, switch to "enums" for invalid event types, and change "fileFilter" to "filter" throughout. Update README to reflect these changes.
|
||||
|
||||
- Update readme to reflect storeFiles and acceptDropsOn changes, plus add documentation for all of the new built-in handlebars helpers
|
||||
|
||||
- commit some files that should be committed
|
||||
|
||||
- -Improve and fix built-in handlebars helpers -Prefix all built-in helpers with "cfs" -Update new example app to reflect helper changes, and improve and fix it a bit, too
|
||||
|
||||
- Merge remote-tracking branch 'upstream/master'
|
||||
|
||||
- update documentation of storeFiles and acceptDropsOn
|
||||
|
||||
- Added credit to @eprochasson
|
||||
|
||||
- *Merged pull-request:* "Should fix issue #45" [#50](https://github.com/zcfs/Meteor-CollectionFS/issues/50) ([eprochasson](https://github.com/eprochasson))
|
||||
|
||||
- *Fixed bug:* "file handlers not showing up in local demo" [#45](https://github.com/zcfs/Meteor-CollectionFS/issues/45)
|
||||
|
||||
- *Merged pull-request:* "Update README.md" [#49](https://github.com/zcfs/Meteor-CollectionFS/issues/49) ([eprochasson](https://github.com/eprochasson))
|
||||
|
||||
- Added meteor style guide for jshint
|
||||
|
||||
- fix package file
|
||||
|
||||
- fixes and improvements to storeFiles() and acceptDropsOn()
|
||||
|
||||
- Use a different saveAs shim
|
||||
|
||||
- Merge branch 'master' of https://github.com/aldeed/Meteor-CollectionFS
|
||||
|
||||
- Fixes and changes to support cfs changes
|
||||
|
||||
- -"cfs" prefix, new helpers, improvements and fixes -include saveAs shim in the package so that download button helper can reliably call it
|
||||
|
||||
- Add dependencies and files
|
||||
|
||||
- Document storeFiles() and acceptDropsOn()
|
||||
|
||||
- *Merged pull-request:* "Built-ins, fixes, etc." [#48](https://github.com/zcfs/Meteor-CollectionFS/issues/48) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Add the new files to the package manifest
|
||||
|
||||
- Add underscore as dependency. It seems that Meteor may soon remove underscore from the core.
|
||||
|
||||
- Minor changes to support fileHanders() and fileFilter() function changes
|
||||
|
||||
- Copy in numeral.js for use by the built-in handlebar helper that displays file size in human readable format. This means the helper supports any of the format strings supported by numeral.js for file sizes.
|
||||
|
||||
- -Add fileFilter() function to specify allowed and disallowed files per collectionFS, based on extensions and/or content types -Add fileIsAllowed() function to easily check whether a particular file is allowed based on the rules set up by fileFilter() -Change all of the passthrough functions (find, findOne, update, remove, allow, deny) to pass through all function arguments more simply and more safely. This allows, for example, using find() instead of find({}). -Change fileHandlers() to extend the object whenever called, which means you can safely call it more than once -Add several utility functions for use in either client or server code
|
||||
|
||||
- -Add storeFiles API -Check that files are allowed by fileFilter before saving -Add acceptDropsOn API -Use .depend() instead of Deps throughout -Pull out _getProgress calc to use in two places -Add isUploading API -Improve isDownloading code
|
||||
|
||||
- New file to hold built-in handlebars helpers, including several initial helpers
|
||||
|
||||
- New file manager example app showing how to use new features, built-in handlebars helpers, etc.
|
||||
|
||||
- *Merged pull-request:* "Minor doc correction" [#47](https://github.com/zcfs/Meteor-CollectionFS/issues/47) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- *Merged pull-request:* "Documentation Improvement" [#46](https://github.com/zcfs/Meteor-CollectionFS/issues/46) ([aldeed](https://github.com/aldeed))
|
||||
|
||||
- Extensively revised README
|
||||
|
||||
Patches by GitHub users [@aldeed](https://github.com/aldeed), [@eprochasson](https://github.com/eprochasson).
|
||||
|
||||
## [v0.2.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.3)
|
||||
#### 04/05/13 by Morten Henriksen
|
||||
- *Fixed bug:* "Binary File Transfers Corrupted? Truncated?" [#41](https://github.com/zcfs/Meteor-CollectionFS/issues/41)
|
||||
|
||||
## [v0.2.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.2)
|
||||
#### 03/05/13 by Morten Henriksen
|
||||
- updated scope for serverConsole
|
||||
|
||||
- *Fixed bug:* "Error when using fresh meteor install" [#40](https://github.com/zcfs/Meteor-CollectionFS/issues/40)
|
||||
|
||||
- more text edits
|
||||
|
||||
- Minor text edits
|
||||
|
||||
- Add credit to README
|
||||
|
||||
- *Merged pull-request:* "Option for file encoding" [#36](https://github.com/zcfs/Meteor-CollectionFS/issues/36) ([nhibner](https://github.com/nhibner))
|
||||
|
||||
- Typo fix.
|
||||
|
||||
- Updated documentation (added encoding to the fileRecord structure).
|
||||
|
||||
- File encoding is stored in the fileRecord.
|
||||
|
||||
- Allow the user to specify an encoding for the buffer when storing on the server.
|
||||
|
||||
- *Merged pull-request:* "Fix backwards incompatibility" [#33](https://github.com/zcfs/Meteor-CollectionFS/issues/33) ([mitar](https://github.com/mitar))
|
||||
|
||||
- Fix backwards incompatibility.
|
||||
|
||||
Patches by GitHub users [@nhibner](https://github.com/nhibner), [@mitar](https://github.com/mitar).
|
||||
|
||||
## [v0.2.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.1)
|
||||
#### 08/04/13 by Morten Henriksen
|
||||
- Minor fixes, added dragndrop, minor refractoring
|
||||
|
||||
- Only work on completed files
|
||||
|
||||
## [v0.2.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.0)
|
||||
#### 06/04/13 by Morten Henriksen
|
||||
- Bump to 0.2.0 - Nice
|
||||
|
||||
- Big speed and refractoring
|
||||
|
||||
## [v0.1.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.9)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added some more doc, deprecated autosubscribe to autopublish instead
|
||||
|
||||
## [v0.1.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.8)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added maxFilehandlers to the doc
|
||||
|
||||
## [devel-#27-fixed] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-#27-fixed)
|
||||
#### 31/03/13 by Morten Henriksen
|
||||
- Added documentation by @petrocket
|
||||
|
||||
- Got filehandlers up and running in bundles
|
||||
|
||||
- Make a seperate thread + connection foreach collectionFS
|
||||
|
||||
## [v0.1.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.7)
|
||||
#### 14/03/13 by Morten Henriksen
|
||||
## [v0.1.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.6)
|
||||
#### 13/01/13 by Morten Henriksen
|
||||
- Converted .length to string to cope with Meteor use of underscore
|
||||
|
||||
## [devel-server-cache] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-server-cache)
|
||||
#### 11/01/13 by Morten Henriksen
|
||||
## [v0.1.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.5)
|
||||
#### 11/01/13 by Morten Henriksen
|
||||
- On going tests - db updates gone, requires refresh to commit changes to db - guess some que not working
|
||||
|
||||
- removed setTimeout when spawn == 1
|
||||
|
||||
## [v0.1.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.4)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.3)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.2)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
## [v0.1.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.1)
|
||||
#### 08/01/13 by Morten Henriksen
|
||||
- Big one, added fileHandler to create cashed versions of the file
|
||||
|
||||
- added made with Meteor in fileHandler example
|
||||
|
||||
- corrected param bug in find and findOne
|
||||
|
||||
- minor fix and update
|
||||
|
||||
- Corrected install guide to use Meteorite
|
||||
|
||||
## [v0.1.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.0)
|
||||
#### 07/01/13 by Morten Henriksen
|
||||
## [v0.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1)
|
||||
#### 07/01/13 by Morten Henriksen
|
||||
- updated file strukture and example
|
||||
|
||||
- Restructure to package system
|
||||
|
||||
- Refractoring filenames and edit package.js
|
||||
|
||||
- Package.js added
|
||||
|
||||
- Added smart.json for Atmosphere packages - Not tested!
|
||||
|
||||
- Readme styling corrected
|
||||
|
||||
- Added comments to the upload, storeFile
|
||||
|
||||
- StoreFile should return fileId or null
|
||||
|
||||
- Added notes about security
|
||||
|
||||
- Added only owner can resume, makes sense for now
|
||||
|
||||
- Added how to make a download...
|
||||
|
||||
- Changed project title git
|
||||
|
||||
- And some more corrections
|
||||
|
||||
- More readme text
|
||||
|
||||
- Added some short reference
|
||||
|
||||
- some more md
|
||||
|
||||
- Initial commit
|
||||
|
||||
20
packages/wekan-cfs-collection/LICENSE.md
Normal file
20
packages/wekan-cfs-collection/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
8
packages/wekan-cfs-collection/README.md
Normal file
8
packages/wekan-cfs-collection/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-collection
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is added when you
|
||||
add the `wekan-cfs-standard-packages` package.
|
||||
260
packages/wekan-cfs-collection/api.common.js
Normal file
260
packages/wekan-cfs-collection/api.common.js
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/** @method FS.Collection.prototype.insert Insert `File` or `FS.File` or remote URL into collection
|
||||
* @public
|
||||
* @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} fileRef File, FS.File, or other data to insert
|
||||
* @param {function} [callback] Callback `function(error, fileObj)`
|
||||
* @returns {FS.File|undefined} The `file object`
|
||||
* [Meteor docs](http://docs.meteor.com/#insert)
|
||||
*/
|
||||
FS.Collection.prototype.insert = function(fileRef, callback) {
|
||||
var self = this;
|
||||
|
||||
if (Meteor.isClient && !callback) {
|
||||
callback = FS.Utility.defaultCallback;
|
||||
}
|
||||
|
||||
// XXX:
|
||||
// We should out factor beginStorage to FS.File.beginStorage
|
||||
// the client side storage adapters should be the one providing
|
||||
// the upload either via http/ddp or direct upload
|
||||
// Could be cool to have a streaming api on the client side
|
||||
// having a createReadStream etc. on the client too...
|
||||
function beginStorage(fileObj) {
|
||||
|
||||
// If on client, begin uploading the data
|
||||
if (Meteor.isClient) {
|
||||
self.options.uploader && self.options.uploader(fileObj);
|
||||
}
|
||||
|
||||
// If on the server, save the binary to a single chunk temp file,
|
||||
// so that it is available when FileWorker calls saveCopies.
|
||||
// This will also trigger file handling from collection observes.
|
||||
else if (Meteor.isServer) {
|
||||
fileObj.createReadStream().pipe(FS.TempStore.createWriteStream(fileObj));
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: would be great if this function could be simplyfied - if even possible?
|
||||
function checkAndInsert(fileObj) {
|
||||
// Check filters. This is called in deny functions, too, but we call here to catch
|
||||
// server inserts and to catch client inserts early, allowing us to call `onInvalid` on
|
||||
// the client and save a trip to the server.
|
||||
if (!self.allowsFile(fileObj)) {
|
||||
return FS.Utility.handleError(callback, 'FS.Collection insert: file does not pass collection filters');
|
||||
}
|
||||
|
||||
// Set collection name
|
||||
fileObj.collectionName = self.name;
|
||||
|
||||
// Insert the file into db
|
||||
// We call cloneFileRecord as an easy way of extracting the properties
|
||||
// that need saving.
|
||||
if (callback) {
|
||||
fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj), function(err, id) {
|
||||
if (err) {
|
||||
if (fileObj._id) {
|
||||
delete fileObj._id;
|
||||
}
|
||||
} else {
|
||||
// Set _id, just to be safe, since this could be before or after the insert method returns
|
||||
fileObj._id = id;
|
||||
// Pass to uploader or stream data to the temp store
|
||||
beginStorage(fileObj);
|
||||
}
|
||||
callback(err, err ? void 0 : fileObj);
|
||||
});
|
||||
} else {
|
||||
fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj));
|
||||
// Pass to uploader or stream data to the temp store
|
||||
beginStorage(fileObj);
|
||||
}
|
||||
return fileObj;
|
||||
}
|
||||
|
||||
// Parse, adjust fileRef
|
||||
if (fileRef instanceof FS.File) {
|
||||
return checkAndInsert(fileRef);
|
||||
} else {
|
||||
// For convenience, allow File, Blob, Buffer, data URI, filepath, URL, etc. to be passed as first arg,
|
||||
// and we will attach that to a new fileobj for them
|
||||
var fileObj = new FS.File(fileRef);
|
||||
if (callback) {
|
||||
fileObj.attachData(fileRef, function attachDataCallback(error) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
checkAndInsert(fileObj);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// We ensure there's a callback on the client, so if there isn't one at this point,
|
||||
// we must be on the server expecting synchronous behavior.
|
||||
fileObj.attachData(fileRef);
|
||||
checkAndInsert(fileObj);
|
||||
}
|
||||
return fileObj;
|
||||
}
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.update Update the file record
|
||||
* @public
|
||||
* @param {FS.File|object} selector
|
||||
* @param {object} modifier
|
||||
* @param {object} [options]
|
||||
* @param {function} [callback]
|
||||
* [Meteor docs](http://docs.meteor.com/#update)
|
||||
*/
|
||||
FS.Collection.prototype.update = function(selector, modifier, options, callback) {
|
||||
var self = this;
|
||||
if (selector instanceof FS.File) {
|
||||
// Make sure the file belongs to this FS.Collection
|
||||
if (selector.collectionName === self.files._name) {
|
||||
return selector.update(modifier, options, callback);
|
||||
} else {
|
||||
// Tried to save a file in the wrong FS.Collection
|
||||
throw new Error('FS.Collection cannot update file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return self.files.update(selector, modifier, options, callback);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.remove Remove the file from the collection
|
||||
* @public
|
||||
* @param {FS.File|object} selector
|
||||
* @param {Function} [callback]
|
||||
* [Meteor docs](http://docs.meteor.com/#remove)
|
||||
*/
|
||||
FS.Collection.prototype.remove = function(selector, callback) {
|
||||
var self = this;
|
||||
if (selector instanceof FS.File) {
|
||||
|
||||
// Make sure the file belongs to this FS.Collection
|
||||
if (selector.collectionName === self.files._name) {
|
||||
return selector.remove(callback);
|
||||
} else {
|
||||
// Tried to remove a file from the wrong FS.Collection
|
||||
throw new Error('FS.Collection cannot remove file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"');
|
||||
}
|
||||
}
|
||||
|
||||
//doesn't work correctly on the client without a callback
|
||||
callback = callback || FS.Utility.defaultCallback;
|
||||
return self.files.remove(selector, callback);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.findOne
|
||||
* @public
|
||||
* @param {[selector](http://docs.meteor.com/#selectors)} selector
|
||||
* [Meteor docs](http://docs.meteor.com/#findone)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
*/
|
||||
// Call findOne on files collection
|
||||
FS.Collection.prototype.findOne = function(selector) {
|
||||
var self = this;
|
||||
return self.files.findOne.apply(self.files, arguments);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.find
|
||||
* @public
|
||||
* @param {[selector](http://docs.meteor.com/#selectors)} selector
|
||||
* [Meteor docs](http://docs.meteor.com/#find)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.find = function(selector) {
|
||||
var self = this;
|
||||
return self.files.find.apply(self.files, arguments);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.allow
|
||||
* @public
|
||||
* @param {object} options
|
||||
* @param {function} options.download Function that checks if the file contents may be downloaded
|
||||
* @param {function} options.insert
|
||||
* @param {function} options.update
|
||||
* @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
* @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
* [Meteor docs](http://docs.meteor.com/#allow)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.allow = function(options) {
|
||||
var self = this;
|
||||
|
||||
// Pull out the custom "download" functions
|
||||
if (options.download) {
|
||||
if (!(options.download instanceof Function)) {
|
||||
throw new Error("allow: Value for `download` must be a function");
|
||||
}
|
||||
self._validators.download.allow.push(options.download);
|
||||
delete options.download;
|
||||
}
|
||||
|
||||
return self.files.allow.call(self.files, options);
|
||||
};
|
||||
|
||||
/** @method FS.Collection.prototype.deny
|
||||
* @public
|
||||
* @param {object} options
|
||||
* @param {function} options.download Function that checks if the file contents may be downloaded
|
||||
* @param {function} options.insert
|
||||
* @param {function} options.update
|
||||
* @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
* @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
* [Meteor docs](http://docs.meteor.com/#deny)
|
||||
* Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
*/
|
||||
FS.Collection.prototype.deny = function(options) {
|
||||
var self = this;
|
||||
|
||||
// Pull out the custom "download" functions
|
||||
if (options.download) {
|
||||
if (!(options.download instanceof Function)) {
|
||||
throw new Error("deny: Value for `download` must be a function");
|
||||
}
|
||||
self._validators.download.deny.push(options.download);
|
||||
delete options.download;
|
||||
}
|
||||
|
||||
return self.files.deny.call(self.files, options);
|
||||
};
|
||||
|
||||
// TODO: Upsert?
|
||||
|
||||
/**
|
||||
* We provide a default implementation that doesn't do anything.
|
||||
* Can be changed by user or packages, such as the default cfs-collection-filters pkg.
|
||||
* @param {FS.File} fileObj File object
|
||||
* @return {Boolean} Should we allow insertion of this file?
|
||||
*/
|
||||
FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
|
||||
return true;
|
||||
};
|
||||
180
packages/wekan-cfs-collection/api.md
Normal file
180
packages/wekan-cfs-collection/api.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
## cfs-collection Public API ##
|
||||
|
||||
CollectionFS, FS.Collection object
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.insert"></a>*fsCollection*.insert(fileRef, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __insert__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}*
|
||||
|
||||
File data reference
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
Callback `function(error, fileObj)`
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The `file object`
|
||||
[Meteor docs](http://docs.meteor.com/#insert)
|
||||
|
||||
> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.update"></a>*fsCollection*.update(selector, modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __modifier__ *{object}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#update)
|
||||
|
||||
> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.remove"></a>*fsCollection*.remove(selector, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __callback__ *{Function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#remove)
|
||||
|
||||
> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.findOne"></a>*fsCollection*.findOne(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __findOne__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#findone)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.find"></a>*fsCollection*.find(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __find__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#find)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allow"></a>*fsCollection*.allow(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allow__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#allow)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.deny"></a>*fsCollection*.deny(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __deny__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#deny)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200)
|
||||
|
||||
|
||||
171
packages/wekan-cfs-collection/common.js
Normal file
171
packages/wekan-cfs-collection/common.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name A name for the collection
|
||||
* @param {Object} options
|
||||
* @param {FS.StorageAdapter[]} options.stores An array of stores in which files should be saved. At least one is required.
|
||||
* @param {Object} [options.filter] Filter definitions
|
||||
* @param {Number} [options.chunkSize=2MB] Override the chunk size in bytes for uploads
|
||||
* @param {Function} [options.uploader] A function to pass FS.File instances after inserting, which will begin uploading them. By default, `FS.HTTP.uploadQueue.uploadFile` is used if the `cfs-upload-http` package is present, or `FS.DDP.uploadQueue.uploadFile` is used if the `cfs-upload-ddp` package is present. You can override with your own, or set to `null` to prevent automatic uploading.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.Collection = function(name, options) {
|
||||
var self = this;
|
||||
|
||||
self.storesLookup = {};
|
||||
|
||||
self.primaryStore = {};
|
||||
|
||||
self.options = {
|
||||
filter: null, //optional
|
||||
stores: [], //required
|
||||
chunkSize: null
|
||||
};
|
||||
|
||||
// Define a default uploader based on which upload packages are present,
|
||||
// preferring HTTP. You may override with your own function or
|
||||
// set to null to skip automatic uploading of data after file insert/update.
|
||||
if (FS.HTTP && FS.HTTP.uploadQueue) {
|
||||
self.options.uploader = FS.HTTP.uploadQueue.uploadFile;
|
||||
} else if (FS.DDP && FS.DDP.uploadQueue) {
|
||||
self.options.uploader = FS.DDP.uploadQueue.uploadFile;
|
||||
}
|
||||
|
||||
// Extend and overwrite options
|
||||
FS.Utility.extend(self.options, options || {});
|
||||
|
||||
// Set the FS.Collection name
|
||||
self.name = name;
|
||||
|
||||
// Make sure at least one store has been supplied.
|
||||
// Usually the stores aren't used on the client, but we need them defined
|
||||
// so that we can access their names and use the first one as the default.
|
||||
if (FS.Utility.isEmpty(self.options.stores)) {
|
||||
throw new Error("You must specify at least one store. Please consult the documentation.");
|
||||
}
|
||||
|
||||
FS.Utility.each(self.options.stores, function(store, i) {
|
||||
// Set the primary store
|
||||
if (i === 0) {
|
||||
self.primaryStore = store;
|
||||
}
|
||||
|
||||
// Check for duplicate naming
|
||||
if (typeof self.storesLookup[store.name] !== 'undefined') {
|
||||
throw new Error('FS.Collection store names must be uniq, duplicate found: ' + store.name);
|
||||
}
|
||||
|
||||
// Set the lookup
|
||||
self.storesLookup[store.name] = store;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
|
||||
// Emit events based on store events
|
||||
store.on('stored', function (storeName, fileObj) {
|
||||
// This is weird, but currently there is a bug where each store will emit the
|
||||
// events for all other stores, too, so we need to make sure that this event
|
||||
// is truly for this store.
|
||||
if (storeName !== store.name)
|
||||
return;
|
||||
// When a file is successfully stored into the store, we emit a "stored" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
var emitted = self.emit('stored', fileObj, store.name);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(fileObj.name({store: store.name}) + ' was successfully saved to the ' + store.name + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on the ' + name + ' collection.');
|
||||
}
|
||||
}
|
||||
fileObj.emit('stored', store.name);
|
||||
});
|
||||
|
||||
store.on('error', function (storeName, error, fileObj) {
|
||||
// This is weird, but currently there is a bug where each store will emit the
|
||||
// events for all other stores, too, so we need to make sure that this event
|
||||
// is truly for this store.
|
||||
if (storeName !== store.name)
|
||||
return;
|
||||
// When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
error = new Error('Error storing file to the ' + store.name + ' store: ' + error.message);
|
||||
var emitted = self.emit('error', error, fileObj, store.name);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
fileObj.emit('error', store.name);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var _filesOptions = {
|
||||
transform: function(doc) {
|
||||
// This should keep the filerecord in the file object updated in reactive
|
||||
// context
|
||||
var result = new FS.File(doc, true);
|
||||
result.collectionName = name;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
if(self.options.idGeneration) _filesOptions.idGeneration = self.options.idGeneration;
|
||||
|
||||
// Enable specifying an alternate driver, to change where the filerecord is stored
|
||||
// Drivers can be created with MongoInternals.RemoteCollectionDriver()
|
||||
if(self.options._driver){
|
||||
_filesOptions._driver = self.options._driver;
|
||||
}
|
||||
|
||||
// Create the 'cfs.' ++ ".filerecord" and use fsFile
|
||||
var collectionName = 'cfs.' + name + '.filerecord';
|
||||
self.files = new Mongo.Collection(collectionName, _filesOptions);
|
||||
|
||||
// For storing custom allow/deny functions
|
||||
self._validators = {
|
||||
download: {allow: [], deny: []}
|
||||
};
|
||||
|
||||
// Set up filters
|
||||
// XXX Should we deprecate the filter option now that this is done with a separate pkg, or just keep it?
|
||||
if (self.filters) {
|
||||
self.filters(self.options.filter);
|
||||
}
|
||||
|
||||
// Save the collection reference (we want it without the 'cfs.' prefix and '.filerecord' suffix)
|
||||
FS._collections[name] = this;
|
||||
|
||||
// Set up observers
|
||||
Meteor.isServer && FS.FileWorker && FS.FileWorker.observe(this);
|
||||
|
||||
// Emit "removed" event on collection
|
||||
self.files.find().observe({
|
||||
removed: function(fileObj) {
|
||||
self.emit('removed', fileObj);
|
||||
}
|
||||
});
|
||||
|
||||
// Emit events based on TempStore events
|
||||
if (FS.TempStore) {
|
||||
FS.TempStore.on('stored', function (fileObj, result) {
|
||||
// When a file is successfully stored into the temp store, we emit an "uploaded" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
var emitted = self.emit('uploaded', fileObj);
|
||||
if (FS.debug && !emitted) {
|
||||
console.log(fileObj.name() + ' was successfully uploaded. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "uploaded" event on the ' + name + ' collection.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FS.TempStore.on('error', function (error, fileObj) {
|
||||
// When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
|
||||
if (fileObj.collectionName === name) {
|
||||
self.emit('error', new Error('Error storing uploaded file to TempStore: ' + error.message), fileObj);
|
||||
}
|
||||
});
|
||||
} else if (Meteor.isServer) {
|
||||
throw new Error("FS.Collection constructor: FS.TempStore must be defined before constructing any FS.Collections.")
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// An FS.Collection can emit events
|
||||
FS.Collection.prototype = new EventEmitter();
|
||||
390
packages/wekan-cfs-collection/internal.api.md
Normal file
390
packages/wekan-cfs-collection/internal.api.md
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["common.js"](common.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
#############################################################################
|
||||
|
||||
COLLECTION FS
|
||||
|
||||
#############################################################################
|
||||
-
|
||||
|
||||
### <a name="FS.Collection"></a>new *fs*.Collection(name, options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __Collection__ is defined in `FS`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __name__ *{string}*
|
||||
|
||||
A name for the collection
|
||||
|
||||
* __options__ *{Object}*
|
||||
* __stores__ *{[FS.StorageAdapter[]](#FS.StorageAdapter[])}*
|
||||
|
||||
An array of stores in which files should be saved. At least one is required.
|
||||
|
||||
* __filter__ *{Object}* (Optional)
|
||||
|
||||
Filter definitions
|
||||
|
||||
* __chunkSize__ *{Number}* (Optional, Default = 131072)
|
||||
|
||||
Override the chunk size in bytes for uploads and downloads
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
|
||||
> ```FS.Collection = function(name, options) { ...``` [common.js:17](common.js#L17)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["api.common.js"](api.common.js) Where: {client|server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.Collection.prototype.insert"></a>*fsCollection*.insert(fileRef, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __insert__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}*
|
||||
|
||||
File data reference
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
Callback `function(error, fileObj)`
|
||||
|
||||
|
||||
__Returns__ *{FS.File}*
|
||||
The `file object`
|
||||
[Meteor docs](http://docs.meteor.com/#insert)
|
||||
|
||||
> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.update"></a>*fsCollection*.update(selector, modifier, [options], [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __update__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __modifier__ *{object}*
|
||||
* __options__ *{object}* (Optional)
|
||||
* __callback__ *{function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#update)
|
||||
|
||||
> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.remove"></a>*fsCollection*.remove(selector, [callback]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __remove__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[FS.File](#FS.File)|object}*
|
||||
* __callback__ *{Function}* (Optional)
|
||||
[Meteor docs](http://docs.meteor.com/#remove)
|
||||
|
||||
> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.findOne"></a>*fsCollection*.findOne(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __findOne__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#findone)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the file object
|
||||
var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' });
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.find"></a>*fsCollection*.find(selector) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __find__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __selector__ *{[selector](http://docs.meteor.com/#selectors)}*
|
||||
[Meteor docs](http://docs.meteor.com/#find)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch();
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.allow"></a>*fsCollection*.allow(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __allow__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be allowed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#allow)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.allow({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.prototype.deny"></a>*fsCollection*.deny(options) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __deny__ is defined in `prototype` of `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{object}*
|
||||
* __download__ *{function}*
|
||||
|
||||
Function that checks if the file contents may be downloaded
|
||||
|
||||
* __insert__ *{function}*
|
||||
* __update__ *{function}*
|
||||
* __remove__ *{function}*
|
||||
|
||||
Functions that look at a proposed modification to the database and return true if it should be denyed
|
||||
|
||||
* __fetch__ *{[string]}* (Optional)
|
||||
|
||||
Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions
|
||||
|
||||
[Meteor docs](http://docs.meteor.com/#deny)
|
||||
Example:
|
||||
```js
|
||||
var images = new FS.Collection( ... );
|
||||
// Get the all file objects
|
||||
var files = images.deny({
|
||||
insert: function(userId, doc) { return true; },
|
||||
update: function(userId, doc, fields, modifier) { return true; },
|
||||
remove: function(userId, doc) { return true; },
|
||||
download: function(userId, fileObj) { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["api.client.js"](api.client.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="_eventCallback"></a>_eventCallback(templateName, selector, dataContext, evt, temp, fsFile) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __dataContext__ *{object}*
|
||||
|
||||
The event datacontext
|
||||
|
||||
* __evt__ *{object}*
|
||||
|
||||
The event object { error, file }
|
||||
|
||||
* __temp__ *{object}*
|
||||
|
||||
The template instance
|
||||
|
||||
* __fsFile__ *{[FS.File](#FS.File)}*
|
||||
|
||||
File that triggered the event
|
||||
|
||||
|
||||
> ```var _eventCallback = function fsEventCallback(templateName, selector, dataContext, evt, temp, fsFile) { ...``` [api.client.js:10](api.client.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="_eachFile"></a>_eachFile(files, metadata, callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __files__ *{array}*
|
||||
|
||||
List of files to iterate over
|
||||
|
||||
* __metadata__ *{object}*
|
||||
|
||||
Data to attach to the files
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
Function to pass the prepared `FS.File` object
|
||||
|
||||
|
||||
> ```var _eachFile = function(files, metadata, callback) { ...``` [api.client.js:36](api.client.js#L36)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.acceptDropsOn"></a>*fsCollection*.acceptDropsOn(templateName, selector, [metadata]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __acceptDropsOn__ is defined in `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __metadata__ *{object|function}* (Optional)
|
||||
|
||||
Data/getter to attach to the file objects
|
||||
|
||||
|
||||
Using this method adds an `uploaded` and `uploadFailed` event to the
|
||||
template events. The event object contains `{ error, file }`
|
||||
|
||||
Example:
|
||||
```css
|
||||
.dropzone {
|
||||
border: 2px dashed silver;
|
||||
height: 5em;
|
||||
padding-top: 3em;
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
-ms-border-radius: 8px;
|
||||
-o-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
```
|
||||
```html
|
||||
<template name="hello">
|
||||
Choose file to upload:<br/>
|
||||
<div id="dropzone" class="dropzone">
|
||||
<div style="text-align: center; color: gray;">Drop file to upload</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
```js
|
||||
Template.hello.events({
|
||||
'uploaded #dropzone': function(event, temp) {
|
||||
console.log('Event Uploaded: ' + event.file._id);
|
||||
}
|
||||
});
|
||||
images.acceptDropsOn('hello', '#dropzone');
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.acceptDropsOn = function(templateName, selector, metadata) { ...``` [api.client.js:99](api.client.js#L99)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.Collection.acceptUploadFrom"></a>*fsCollection*.acceptUploadFrom(templateName, selector, [metadata]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __acceptUploadFrom__ is defined in `FS.Collection`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __templateName__ *{string}*
|
||||
|
||||
Name of template to apply events on
|
||||
|
||||
* __selector__ *{string}*
|
||||
|
||||
The element selector eg. "#uploadField"
|
||||
|
||||
* __metadata__ *{object|function}* (Optional)
|
||||
|
||||
Data/getter to attach to the file objects
|
||||
|
||||
|
||||
Using this method adds an `uploaded` and `uploadFailed` event to the
|
||||
template events. The event object contains `{ error, file }`
|
||||
|
||||
Example:
|
||||
```html
|
||||
<template name="hello">
|
||||
Choose file to upload:<br/>
|
||||
<input type="file" id="files" multiple/>
|
||||
</template>
|
||||
```
|
||||
```js
|
||||
Template.hello.events({
|
||||
'uploaded #files': function(event, temp) {
|
||||
console.log('Event Uploaded: ' + event.file._id);
|
||||
}
|
||||
});
|
||||
images.acceptUploadFrom('hello', '#files');
|
||||
```
|
||||
|
||||
> ```FS.Collection.prototype.acceptUploadFrom = function(templateName, selector, metadata) { ...``` [api.client.js:156](api.client.js#L156)
|
||||
|
||||
|
||||
43
packages/wekan-cfs-collection/package.js
Normal file
43
packages/wekan-cfs-collection/package.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-collection',
|
||||
version: '0.5.5',
|
||||
summary: 'CollectionFS, FS.Collection object',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-collection.git'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
// CFS
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-tempstore@0.1.4',
|
||||
// Core
|
||||
'deps',
|
||||
'check',
|
||||
'livedata',
|
||||
'mongo-livedata',
|
||||
// Other
|
||||
'raix:eventemitter@0.1.1'
|
||||
]);
|
||||
|
||||
// Weak dependencies for uploaders
|
||||
api.use(['wekan-cfs-upload-http@0.0.20', 'wekan-cfs-upload-ddp@0.0.17'], { weak: true });
|
||||
|
||||
api.addFiles([
|
||||
'common.js',
|
||||
'api.common.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'common.js',
|
||||
'api.common.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(['wekan-cfs-standard-packages', 'wekan-cfs-gridfs', 'tinytest', 'underscore', 'test-helpers']);
|
||||
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
});
|
||||
33
packages/wekan-cfs-collection/tests/client-tests.js
Normal file
33
packages/wekan-cfs-collection/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-collection - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
/*
|
||||
* TODO FS.Collection Client Tests
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
96
packages/wekan-cfs-collection/tests/server-tests.js
Normal file
96
packages/wekan-cfs-collection/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
var fileCollection = new FS.Collection('files', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('files')
|
||||
]
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - insert URL - sync - one', function (test) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
// One
|
||||
try {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg");
|
||||
test.isTrue(true);
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add('cfs-collection - insert URL - sync - loop', function (test) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
try {
|
||||
for (var i = 0, len = 10; i < len; i++) {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg");
|
||||
}
|
||||
test.isTrue(true);
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
}
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-collection - insert URL - async - one', function (test, next) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
// One
|
||||
try {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) {
|
||||
test.isNull(error);
|
||||
test.instanceOf(result, FS.File);
|
||||
next();
|
||||
});
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-collection - insert URL - async - loop', function (test, next) {
|
||||
// XXX should switch to a local URL we host
|
||||
|
||||
try {
|
||||
var done = 0;
|
||||
for (var i = 0, len = 10; i < len; i++) {
|
||||
fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) {
|
||||
test.isNull(error);
|
||||
test.instanceOf(result, FS.File);
|
||||
done++;
|
||||
if (done === 10) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
test.isFalse(!!err, "URL insert failed");
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equalactual, expected, message, not
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
//test.equal(a, b, msg)
|
||||
5
packages/wekan-cfs-data-man/.travis.yml
Normal file
5
packages/wekan-cfs-data-man/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
20
packages/wekan-cfs-data-man/LICENSE.md
Normal file
20
packages/wekan-cfs-data-man/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com, and [@aldeed](https://github.com/aldeed), aka Eric Dobbertin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
8
packages/wekan-cfs-data-man/README.md
Normal file
8
packages/wekan-cfs-data-man/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
wekan-cfs-data-man [](https://travis-ci.org/CollectionFS/Meteor-data-man)
|
||||
=========================
|
||||
|
||||
Who can handle your arbitrary data? The DataMan can.
|
||||
|
||||
This is a package used by CollectionFS to attach data to file objects. Could be used for other things, though.
|
||||
|
||||
[Public API](api.md)
|
||||
309
packages/wekan-cfs-data-man/api.md
Normal file
309
packages/wekan-cfs-data-man/api.md
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
## data-man Public API ##
|
||||
|
||||
A data manager, allowing you to attach various types of data and get it back in various other types
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Client</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBlob"></a>*dataman*.getBlob(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBlob__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, blob)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Blob representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBinary"></a>*dataman*.getBinary([start], [end], callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBinary__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __start__ *{Number}* (Optional)
|
||||
|
||||
First byte position to read.
|
||||
|
||||
* __end__ *{Number}* (Optional)
|
||||
|
||||
Last byte position to read.
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, binaryData)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Uint8Array representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveAs"></a>*dataman*.saveAs([filename]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __saveAs__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filename__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
Tells the browser to save the data like a normal downloaded file,
|
||||
using the provided filename.
|
||||
|
||||
|
||||
> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBuffer"></a>*dataman*.getBuffer([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveToFile"></a>*dataman*.saveToFile() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __saveToFile__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Saves this data to a filepath on the local filesystem.
|
||||
|
||||
> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the data URI.
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.createReadStream"></a>*dataman*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.size"></a>*dataman*.size([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the size in bytes of the data.
|
||||
|
||||
> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer"></a>new *dataman*.Buffer(buffer, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Buffer__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __buffer__ *{Buffer}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.DataURI"></a>new *dataman*.DataURI(dataUri) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __DataURI__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataUri__ *{String}*
|
||||
|
||||
|
||||
> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath"></a>new *dataman*.FilePath(filepath, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __FilePath__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filepath__ *{String}*
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type. Will lookup from file if not passed.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL"></a>new *dataman*.URL(url, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __URL__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __url__ *{String}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10)
|
||||
|
||||
|
||||
166
packages/wekan-cfs-data-man/client/Blob.js
Normal file
166
packages/wekan-cfs-data-man/client/Blob.js
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/* Blob.js
|
||||
* A Blob implementation.
|
||||
* 2013-12-27
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* By Devin Samarin, https://github.com/eboyjr
|
||||
* License: X11/MIT
|
||||
* See LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self, unescape */
|
||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||
|
||||
if (!(typeof Blob === "function" || typeof Blob === "object") || typeof URL === "undefined")
|
||||
if ((typeof Blob === "function" || typeof Blob === "object") && typeof webkitURL !== "undefined") self.URL = webkitURL;
|
||||
else var Blob = (function (view) {
|
||||
"use strict";
|
||||
|
||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
|
||||
var
|
||||
get_class = function(object) {
|
||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||
}
|
||||
, FakeBlobBuilder = function BlobBuilder() {
|
||||
this.data = [];
|
||||
}
|
||||
, FakeBlob = function Blob(data, type, encoding) {
|
||||
this.data = data;
|
||||
this.size = data.length;
|
||||
this.type = type;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
, FBB_proto = FakeBlobBuilder.prototype
|
||||
, FB_proto = FakeBlob.prototype
|
||||
, FileReaderSync = view.FileReaderSync
|
||||
, FileException = function(type) {
|
||||
this.code = this[this.name = type];
|
||||
}
|
||||
, file_ex_codes = (
|
||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||
).split(" ")
|
||||
, file_ex_code = file_ex_codes.length
|
||||
, real_URL = view.URL || view.webkitURL || view
|
||||
, real_create_object_URL = real_URL.createObjectURL
|
||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||
, URL = real_URL
|
||||
, btoa = view.btoa
|
||||
, atob = view.atob
|
||||
|
||||
, ArrayBuffer = view.ArrayBuffer
|
||||
, Uint8Array = view.Uint8Array
|
||||
;
|
||||
FakeBlob.fake = FB_proto.fake = true;
|
||||
while (file_ex_code--) {
|
||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||
}
|
||||
if (!real_URL.createObjectURL) {
|
||||
URL = view.URL = {};
|
||||
}
|
||||
URL.createObjectURL = function(blob) {
|
||||
var
|
||||
type = blob.type
|
||||
, data_URI_header
|
||||
;
|
||||
if (type === null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
if (blob instanceof FakeBlob) {
|
||||
data_URI_header = "data:" + type;
|
||||
if (blob.encoding === "base64") {
|
||||
return data_URI_header + ";base64," + blob.data;
|
||||
} else if (blob.encoding === "URI") {
|
||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||
} if (btoa) {
|
||||
return data_URI_header + ";base64," + btoa(blob.data);
|
||||
} else {
|
||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||
}
|
||||
} else if (real_create_object_URL) {
|
||||
return real_create_object_URL.call(real_URL, blob);
|
||||
}
|
||||
};
|
||||
URL.revokeObjectURL = function(object_URL) {
|
||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||
real_revoke_object_URL.call(real_URL, object_URL);
|
||||
}
|
||||
};
|
||||
FBB_proto.append = function(data/*, endings*/) {
|
||||
var bb = this.data;
|
||||
// decode data to a binary string
|
||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||
var
|
||||
str = ""
|
||||
, buf = new Uint8Array(data)
|
||||
, i = 0
|
||||
, buf_len = buf.length
|
||||
;
|
||||
for (; i < buf_len; i++) {
|
||||
str += String.fromCharCode(buf[i]);
|
||||
}
|
||||
bb.push(str);
|
||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||
if (FileReaderSync) {
|
||||
var fr = new FileReaderSync;
|
||||
bb.push(fr.readAsBinaryString(data));
|
||||
} else {
|
||||
// async FileReader won't work as BlobBuilder is sync
|
||||
throw new FileException("NOT_READABLE_ERR");
|
||||
}
|
||||
} else if (data instanceof FakeBlob) {
|
||||
if (data.encoding === "base64" && atob) {
|
||||
bb.push(atob(data.data));
|
||||
} else if (data.encoding === "URI") {
|
||||
bb.push(decodeURIComponent(data.data));
|
||||
} else if (data.encoding === "raw") {
|
||||
bb.push(data.data);
|
||||
}
|
||||
} else {
|
||||
if (typeof data !== "string") {
|
||||
data += ""; // convert unsupported types to strings
|
||||
}
|
||||
// decode UTF-16 to binary string
|
||||
bb.push(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
};
|
||||
FBB_proto.getBlob = function(type) {
|
||||
if (!arguments.length) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(this.data.join(""), type, "raw");
|
||||
};
|
||||
FBB_proto.toString = function() {
|
||||
return "[object BlobBuilder]";
|
||||
};
|
||||
FB_proto.slice = function(start, end, type) {
|
||||
var args = arguments.length;
|
||||
if (args < 3) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(
|
||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||
, type
|
||||
, this.encoding
|
||||
);
|
||||
};
|
||||
FB_proto.toString = function() {
|
||||
return "[object Blob]";
|
||||
};
|
||||
return FakeBlobBuilder;
|
||||
}(view));
|
||||
|
||||
return function Blob(blobParts, options) {
|
||||
var type = options ? (options.type || "") : "";
|
||||
var builder = new BlobBuilder();
|
||||
if (blobParts) {
|
||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||
builder.append(blobParts[i]);
|
||||
}
|
||||
}
|
||||
return builder.getBlob(type);
|
||||
};
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
||||
302
packages/wekan-cfs-data-man/client/data-man-api.js
Normal file
302
packages/wekan-cfs-data-man/client/data-man-api.js
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* @method DataMan
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {File|Blob|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
|
||||
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
*/
|
||||
DataMan = function DataMan(data, type) {
|
||||
var self = this;
|
||||
|
||||
if (!data) {
|
||||
throw new Error("DataMan constructor requires a data argument");
|
||||
}
|
||||
|
||||
// The end result of all this is that we will have one of the following set:
|
||||
// - self.blob
|
||||
// - self.url
|
||||
// Unless we already have in-memory data, we don't load anything into memory
|
||||
// and instead rely on obtaining a read stream when the time comes.
|
||||
if (typeof File !== "undefined" && data instanceof File) {
|
||||
self.blob = data; // File inherits from Blob so this is OK
|
||||
self._type = data.type;
|
||||
} else if (typeof Blob !== "undefined" && data instanceof Blob) {
|
||||
self.blob = data;
|
||||
self._type = data.type;
|
||||
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer || EJSON.isBinary(data)) {
|
||||
if (typeof Blob === "undefined") {
|
||||
throw new Error("Browser must support Blobs to handle an ArrayBuffer or Uint8Array");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer or Uint8Array");
|
||||
}
|
||||
self.blob = new Blob([data], {type: type});
|
||||
self._type = type;
|
||||
} else if (typeof data === "string") {
|
||||
if (data.slice(0, 5) === "data:") {
|
||||
self._type = data.slice(5, data.indexOf(';'));
|
||||
self.blob = dataURItoBlob(data, self._type);
|
||||
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a URL");
|
||||
}
|
||||
self.url = data;
|
||||
self._type = type;
|
||||
} else {
|
||||
throw new Error("DataMan constructor received unrecognized data string");
|
||||
}
|
||||
} else {
|
||||
throw new Error("DataMan constructor received data that it doesn't support");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBlob
|
||||
* @public
|
||||
* @param {Function} [callback] - callback(error, blob)
|
||||
* @returns {undefined|Blob}
|
||||
*
|
||||
* Passes a Blob representing this data to a callback or returns
|
||||
* the Blob if no callback is provided. A callback is required
|
||||
* if getting a Blob for a URL.
|
||||
*/
|
||||
DataMan.prototype.getBlob = function dataManGetBlob(callback) {
|
||||
var self = this;
|
||||
|
||||
if (callback) {
|
||||
if (self.blob) {
|
||||
callback(null, self.blob);
|
||||
} else if (self.url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', self.url, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.onload = function(data) {
|
||||
self.blob = xhr.response;
|
||||
callback(null, self.blob);
|
||||
};
|
||||
xhr.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
} else {
|
||||
if (self.url)
|
||||
throw new Error('DataMan.getBlob requires a callback when managing a URL');
|
||||
return self.blob;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBinary
|
||||
* @public
|
||||
* @param {Number} [start] - First byte position to read.
|
||||
* @param {Number} [end] - Last byte position to read.
|
||||
* @param {Function} callback - callback(error, binaryData)
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Passes a Uint8Array representing this data to a callback.
|
||||
*/
|
||||
DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof start === "function") {
|
||||
callback = start;
|
||||
}
|
||||
callback = callback || defaultCallback;
|
||||
|
||||
function read(blob) {
|
||||
if (typeof FileReader === "undefined") {
|
||||
callback(new Error("Browser does not support FileReader"));
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
callback(null, new Uint8Array(evt.target.result));
|
||||
};
|
||||
reader.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
if (typeof start === "number" && typeof end === "number") {
|
||||
var size = blob.size;
|
||||
// Return the requested chunk of binary data
|
||||
if (start >= size) {
|
||||
callback(new Error("DataMan.getBinary: start position beyond end of data (" + size + ")"));
|
||||
return;
|
||||
}
|
||||
end = Math.min(size, end);
|
||||
|
||||
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
|
||||
if (typeof slice === 'undefined') {
|
||||
callback(new Error('Browser does not support File.slice'));
|
||||
return;
|
||||
}
|
||||
|
||||
read(slice.call(blob, start, end, self._type));
|
||||
} else {
|
||||
// Return the entire binary data
|
||||
read(blob);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/** @method DataMan.prototype.saveAs
|
||||
* @public
|
||||
* @param {String} [filename]
|
||||
* @return {undefined}
|
||||
*
|
||||
* Tells the browser to save the data like a normal downloaded file,
|
||||
* using the provided filename.
|
||||
*
|
||||
*/
|
||||
DataMan.prototype.saveAs = function dataManSaveAs(filename) {
|
||||
var self = this;
|
||||
|
||||
if (typeof window === "undefined")
|
||||
throw new Error("window must be defined to use saveLocal");
|
||||
|
||||
if (!window.saveAs) {
|
||||
console.warn('DataMan.saveAs: window.saveAs not supported by this browser - add cfs-filesaver package');
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
window.saveAs(blob, filename);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getDataUri
|
||||
* @public
|
||||
* @param {function} callback callback(err, dataUri)
|
||||
*/
|
||||
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
|
||||
// XXX: We could consider using: URL.createObjectURL(blob);
|
||||
// This will create a reference to the blob data instead of a clone
|
||||
// This is part of the File API - as the rest - Not sure how to generally
|
||||
// support from IE10, FF26, Chrome 31, safari 7, opera 19, ios 6, android 4
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof callback !== 'function')
|
||||
throw new Error("getDataUri requires callback function");
|
||||
|
||||
if (typeof FileReader === "undefined") {
|
||||
callback(new Error("Browser does not support FileReader"));
|
||||
return;
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var dataUri = event.target.result;
|
||||
callback(null, dataUri);
|
||||
};
|
||||
fileReader.onerror = function(err) {
|
||||
callback(err);
|
||||
};
|
||||
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
fileReader.readAsDataURL(blob);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.size
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, size)
|
||||
*
|
||||
* Passes the size of the data to the callback, if provided,
|
||||
* or returns it. A callback is required to get the size of a URL on the client.
|
||||
*/
|
||||
DataMan.prototype.size = function dataManSize(callback) {
|
||||
var self = this;
|
||||
|
||||
if (callback) {
|
||||
if (typeof self._size === "number") {
|
||||
callback(null, self._size);
|
||||
} else {
|
||||
self.getBlob(function (error, blob) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
self._size = blob.size;
|
||||
callback(null, self._size);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (self.url) {
|
||||
throw new Error("On the client, DataMan.size requires a callback when getting size for a URL on the client");
|
||||
} else if (typeof self._size === "number") {
|
||||
return self._size;
|
||||
} else {
|
||||
var blob = self.getBlob();
|
||||
self._size = blob.size;
|
||||
return self._size;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.type
|
||||
* @public
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.prototype.type = function dataManType() {
|
||||
return this._type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method dataURItoBlob
|
||||
* @private
|
||||
* @param {String} dataURI The data URI
|
||||
* @param {String} dataTYPE The content type
|
||||
* @returns {Blob} A new Blob instance
|
||||
*
|
||||
* Converts a data URI to a Blob.
|
||||
*/
|
||||
function dataURItoBlob(dataURI, dataTYPE) {
|
||||
var str = atob(dataURI.split(',')[1]), array = [];
|
||||
for(var i = 0; i < str.length; i++) array.push(str.charCodeAt(i));
|
||||
return new Blob([new Uint8Array(array)], {type: dataTYPE});
|
||||
}
|
||||
|
||||
/**
|
||||
* @method defaultCallback
|
||||
* @private
|
||||
* @param {Error} [err]
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Can be used as a default callback for client methods that need a callback.
|
||||
* Simply throws the provided error if there is one.
|
||||
*/
|
||||
function defaultCallback(err) {
|
||||
if (err) {
|
||||
// Show gentle error if Meteor error
|
||||
if (err instanceof Meteor.Error) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
// Normal error, just throw error
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
674
packages/wekan-cfs-data-man/internal.api.md
Normal file
674
packages/wekan-cfs-data-man/internal.api.md
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["client/Blob.js"](client/Blob.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="if "></a>if {any} <sub><i>Client</i></sub> ###
|
||||
|
||||
```
|
||||
Blob.js
|
||||
A Blob implementation.
|
||||
2013-06-20
|
||||
By Eli Grey, http:
|
||||
By Devin Samarin, https:
|
||||
License: X11/MIT
|
||||
See LICENSE.md
|
||||
```
|
||||
|
||||
|
||||
> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="if "></a>if {any} <sub><i>Client</i></sub> ###
|
||||
|
||||
```
|
||||
global unescape jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true
|
||||
```
|
||||
|
||||
> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["client/data-man-api.js"](client/data-man-api.js) Where: {client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Client</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBlob"></a>*dataman*.getBlob(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBlob__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, blob)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Blob representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBinary"></a>*dataman*.getBinary([start], [end], callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getBinary__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __start__ *{Number}* (Optional)
|
||||
|
||||
First byte position to read.
|
||||
|
||||
* __end__ *{Number}* (Optional)
|
||||
|
||||
Last byte position to read.
|
||||
|
||||
* __callback__ *{Function}*
|
||||
|
||||
callback(error, binaryData)
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Passes a Uint8Array representing this data to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveAs"></a>*dataman*.saveAs([filename]) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __saveAs__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filename__ *{String}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
Tells the browser to save the data like a normal downloaded file,
|
||||
using the provided filename.
|
||||
|
||||
|
||||
> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri(callback) <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Client</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="dataURItoBlob"></a>dataURItoBlob(dataURI, dataTYPE) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataURI__ *{String}*
|
||||
|
||||
The data URI
|
||||
|
||||
* __dataTYPE__ *{String}*
|
||||
|
||||
The content type
|
||||
|
||||
|
||||
__Returns__ *{Blob}*
|
||||
A new Blob instance
|
||||
|
||||
|
||||
Converts a data URI to a Blob.
|
||||
|
||||
> ```function dataURItoBlob(dataURI, dataTYPE) { ...``` [client/data-man-api.js:240](client/data-man-api.js#L240)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="defaultCallback"></a>defaultCallback([err]) <sub><i>undefined</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __err__ *{[Error](#Error)}* (Optional)
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Can be used as a default callback for client methods that need a callback.
|
||||
Simply throws the provided error if there is one.
|
||||
|
||||
> ```function defaultCallback(err) { ...``` [client/data-man-api.js:255](client/data-man-api.js#L255)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-api.js"](server/data-man-api.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan"></a>new DataMan(data, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}*
|
||||
|
||||
The data that you want to manipulate.
|
||||
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
|
||||
|
||||
|
||||
> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getBuffer"></a>*dataman*.getBuffer([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
|
||||
> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.saveToFile"></a>*dataman*.saveToFile() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __saveToFile__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Saves this data to a filepath on the local filesystem.
|
||||
|
||||
> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.getDataUri"></a>*dataman*.getDataUri([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the data URI.
|
||||
|
||||
> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.createReadStream"></a>*dataman*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.size"></a>*dataman*.size([callback]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __size__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}* (Optional)
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
If no callback, returns the size in bytes of the data.
|
||||
|
||||
> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.prototype.type"></a>*dataman*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __type__ is defined in `prototype` of `DataMan`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-buffer.js"](server/data-man-buffer.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.Buffer"></a>new *dataman*.Buffer(buffer, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Buffer__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __buffer__ *{Buffer}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.getBuffer"></a>*datamanBuffer*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) { ...``` [server/data-man-buffer.js:24](server/data-man-buffer.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.getDataUri"></a>*datamanBuffer*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data in the buffer to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) { ...``` [server/data-man-buffer.js:35](server/data-man-buffer.js#L35)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.createReadStream"></a>*datamanBuffer*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() { ...``` [server/data-man-buffer.js:51](server/data-man-buffer.js#L51)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.size"></a>*datamanBuffer*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Passes the size in bytes of the data in the buffer to a callback.
|
||||
|
||||
> ```DataMan.Buffer.prototype.size = function dataManBufferSize(callback) { ...``` [server/data-man-buffer.js:62](server/data-man-buffer.js#L62)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.Buffer.prototype.type"></a>*datamanBuffer*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.Buffer`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.Buffer.prototype.type = function dataManBufferType() { ...``` [server/data-man-buffer.js:80](server/data-man-buffer.js#L80)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-datauri.js"](server/data-man-datauri.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.DataURI"></a>new *dataman*.DataURI(dataUri) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __DataURI__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __dataUri__ *{String}*
|
||||
|
||||
|
||||
> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-filepath.js"](server/data-man-filepath.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.FilePath"></a>new *dataman*.FilePath(filepath, [type]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __FilePath__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __filepath__ *{String}*
|
||||
* __type__ *{String}* (Optional)
|
||||
|
||||
The data content (MIME) type. Will lookup from file if not passed.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.getBuffer"></a>*datamanFilepath*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) { ...``` [server/data-man-filepath.js:25](server/data-man-filepath.js#L25)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.getDataUri"></a>*datamanFilepath*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) { ...``` [server/data-man-filepath.js:43](server/data-man-filepath.js#L43)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.createReadStream"></a>*datamanFilepath*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() { ...``` [server/data-man-filepath.js:67](server/data-man-filepath.js#L67)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.size"></a>*datamanFilepath*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Passes the size in bytes of the data to a callback.
|
||||
|
||||
> ```DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) { ...``` [server/data-man-filepath.js:79](server/data-man-filepath.js#L79)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.FilePath.prototype.type"></a>*datamanFilepath*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.FilePath`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.FilePath.prototype.type = function dataManFilePathType() { ...``` [server/data-man-filepath.js:104](server/data-man-filepath.js#L104)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["server/data-man-url.js"](server/data-man-url.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="DataMan.URL"></a>new *dataman*.URL(url, type) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __URL__ is defined in `DataMan`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __url__ *{String}*
|
||||
* __type__ *{String}*
|
||||
|
||||
The data content (MIME) type.
|
||||
|
||||
|
||||
|
||||
> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.getBuffer"></a>*datamanUrl*.getBuffer(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getBuffer__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, buffer)
|
||||
|
||||
|
||||
__Returns__ *{Buffer|undefined}*
|
||||
|
||||
|
||||
Passes a Buffer representing the data at the URL to a callback.
|
||||
|
||||
> ```DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) { ...``` [server/data-man-url.js:24](server/data-man-url.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.getDataUri"></a>*datamanUrl*.getDataUri(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __getDataUri__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, dataUri)
|
||||
|
||||
|
||||
|
||||
Passes a data URI representing the data at the URL to a callback.
|
||||
|
||||
> ```DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) { ...``` [server/data-man-url.js:57](server/data-man-url.js#L57)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.createReadStream"></a>*datamanUrl*.createReadStream() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __createReadStream__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
|
||||
Returns a read stream for the data.
|
||||
|
||||
> ```DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() { ...``` [server/data-man-url.js:85](server/data-man-url.js#L85)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.size"></a>*datamanUrl*.size(callback) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __size__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __callback__ *{function}*
|
||||
|
||||
callback(err, size)
|
||||
|
||||
|
||||
|
||||
Returns the size in bytes of the data at the URL.
|
||||
|
||||
> ```DataMan.URL.prototype.size = function dataManUrlSize(callback) { ...``` [server/data-man-url.js:97](server/data-man-url.js#L97)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="DataMan.URL.prototype.type"></a>*datamanUrl*.type() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
*This method __type__ is defined in `prototype` of `DataMan.URL`*
|
||||
|
||||
|
||||
Returns the type of the data.
|
||||
|
||||
> ```DataMan.URL.prototype.type = function dataManUrlType() { ...``` [server/data-man-url.js:121](server/data-man-url.js#L121)
|
||||
|
||||
|
||||
48
packages/wekan-cfs-data-man/package.js
Normal file
48
packages/wekan-cfs-data-man/package.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-data-man',
|
||||
version: '0.0.6',
|
||||
summary: 'A data manager, allowing you to attach various types of data and get it back in various other types',
|
||||
git: 'https://github.com/zcfs/Meteor-data-man.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
mime: "1.2.11",
|
||||
'buffer-stream-reader': "0.1.1",
|
||||
//request: "2.44.0",
|
||||
// We use a specific commit from a fork of "request" package for now; we need fix for
|
||||
// https://github.com/mikeal/request/issues/887 (https://github.com/zcfs/Meteor-CollectionFS/issues/347)
|
||||
request: "https://github.com/wekan/request",
|
||||
temp: "0.7.0" // for tests only
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['ejson']);
|
||||
|
||||
api.use(['wekan-cfs-filesaver@0.0.6'], {weak: true});
|
||||
|
||||
api.export('DataMan');
|
||||
|
||||
api.addFiles([
|
||||
'client/Blob.js', //polyfill for browsers without Blob constructor; currently necessary for phantomjs support, too
|
||||
'client/data-man-api.js'
|
||||
], 'client');
|
||||
|
||||
api.addFiles([
|
||||
'server/data-man-api.js',
|
||||
'server/data-man-buffer.js',
|
||||
'server/data-man-datauri.js',
|
||||
'server/data-man-filepath.js',
|
||||
'server/data-man-url.js',
|
||||
'server/data-man-readstream.js'
|
||||
], 'server');
|
||||
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(['wekan-cfs-data-man', 'http', 'tinytest', 'test-helpers', 'wekan-cfs-http-methods@0.0.29']);
|
||||
|
||||
api.addFiles(['tests/common.js', 'tests/client-tests.js'], 'client');
|
||||
api.addFiles(['tests/common.js', 'tests/server-tests.js'], 'server');
|
||||
});
|
||||
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
176
packages/wekan-cfs-data-man/server/data-man-api.js
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/* global DataMan:true, Buffer */
|
||||
|
||||
var fs = Npm.require("fs");
|
||||
var Readable = Npm.require('stream').Readable;
|
||||
|
||||
/**
|
||||
* @method DataMan
|
||||
* @public
|
||||
* @constructor
|
||||
* @param {Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
|
||||
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
|
||||
* @param {Object} [options] Currently used only to pass options for the GET request when `data` is a URL.
|
||||
*/
|
||||
DataMan = function DataMan(data, type, options) {
|
||||
var self = this, buffer;
|
||||
|
||||
if (!data) {
|
||||
throw new Error("DataMan constructor requires a data argument");
|
||||
}
|
||||
|
||||
// The end result of all this is that we will have this.source set to a correct
|
||||
// data type handler. We are simply detecting what the data arg is.
|
||||
//
|
||||
// Unless we already have in-memory data, we don't load anything into memory
|
||||
// and instead rely on obtaining a read stream when the time comes.
|
||||
if (typeof Buffer !== "undefined" && data instanceof Buffer) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Buffer");
|
||||
}
|
||||
self.source = new DataMan.Buffer(data, type);
|
||||
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer");
|
||||
}
|
||||
buffer = new Buffer(new Uint8Array(data));
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (EJSON.isBinary(data)) {
|
||||
if (typeof Buffer === "undefined") {
|
||||
throw new Error("Buffer support required to handle an ArrayBuffer");
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a Uint8Array");
|
||||
}
|
||||
buffer = new Buffer(data);
|
||||
self.source = new DataMan.Buffer(buffer, type);
|
||||
} else if (typeof Readable !== "undefined" && data instanceof Readable) {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a stream.Readable");
|
||||
}
|
||||
self.source = new DataMan.ReadStream(data, type);
|
||||
} else if (typeof data === "string") {
|
||||
if (data.slice(0, 5) === "data:") {
|
||||
self.source = new DataMan.DataURI(data);
|
||||
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
|
||||
if (!type) {
|
||||
throw new Error("DataMan constructor requires a type argument when passed a URL");
|
||||
}
|
||||
self.source = new DataMan.URL(data, type, options);
|
||||
} else {
|
||||
// assume it's a filepath
|
||||
self.source = new DataMan.FilePath(data, type);
|
||||
}
|
||||
} else {
|
||||
throw new Error("DataMan constructor received data that it doesn't support");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getBuffer
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, buffer)
|
||||
* @returns {Buffer|undefined}
|
||||
*
|
||||
* Returns a Buffer representing this data, or passes the Buffer to a callback.
|
||||
*/
|
||||
DataMan.prototype.getBuffer = function dataManGetBuffer(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getBuffer(callback) : Meteor.wrapAsync(bind(self.source.getBuffer, self.source))();
|
||||
};
|
||||
|
||||
function _saveToFile(readStream, filePath, callback) {
|
||||
var writeStream = fs.createWriteStream(filePath);
|
||||
writeStream.on('close', Meteor.bindEnvironment(function () {
|
||||
callback();
|
||||
}, function (error) { callback(error); }));
|
||||
writeStream.on('error', Meteor.bindEnvironment(function (error) {
|
||||
callback(error);
|
||||
}, function (error) { callback(error); }));
|
||||
readStream.pipe(writeStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.saveToFile
|
||||
* @public
|
||||
* @param {String} filePath
|
||||
* @param {Function} callback
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Saves this data to a filepath on the local filesystem.
|
||||
*/
|
||||
DataMan.prototype.saveToFile = function dataManSaveToFile(filePath, callback) {
|
||||
var readStream = this.createReadStream();
|
||||
return callback ? _saveToFile(readStream, filePath, callback) : Meteor.wrapAsync(_saveToFile)(readStream, filePath);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.getDataUri
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, dataUri)
|
||||
*
|
||||
* If no callback, returns the data URI.
|
||||
*/
|
||||
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.getDataUri(callback) : Meteor.wrapAsync(bind(self.source.getDataUri, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.createReadStream
|
||||
* @public
|
||||
*
|
||||
* Returns a read stream for the data.
|
||||
*/
|
||||
DataMan.prototype.createReadStream = function dataManCreateReadStream() {
|
||||
return this.source.createReadStream();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.size
|
||||
* @public
|
||||
* @param {function} [callback] callback(err, size)
|
||||
*
|
||||
* If no callback, returns the size in bytes of the data.
|
||||
*/
|
||||
DataMan.prototype.size = function dataManSize(callback) {
|
||||
var self = this;
|
||||
return callback ? self.source.size(callback) : Meteor.wrapAsync(bind(self.source.size, self.source))();
|
||||
};
|
||||
|
||||
/**
|
||||
* @method DataMan.prototype.type
|
||||
* @public
|
||||
*
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
DataMan.prototype.type = function dataManType() {
|
||||
return this.source.type();
|
||||
};
|
||||
|
||||
/*
|
||||
* "bind" shim; from underscorejs, but we avoid a dependency
|
||||
*/
|
||||
var slice = Array.prototype.slice;
|
||||
var nativeBind = Function.prototype.bind;
|
||||
var ctor = function(){};
|
||||
function isFunction(obj) {
|
||||
return Object.prototype.toString.call(obj) == '[object Function]';
|
||||
}
|
||||
function bind(func, context) {
|
||||
var args, bound;
|
||||
if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
if (!isFunction(func)) throw new TypeError;
|
||||
args = slice.call(arguments, 2);
|
||||
return bound = function() {
|
||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
||||
ctor.prototype = func.prototype;
|
||||
var self = new ctor;
|
||||
ctor.prototype = null;
|
||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
||||
if (Object(result) === result) return result;
|
||||
return self;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue