mirror of
https://github.com/foambubble/foam.git
synced 2026-01-11 06:58:11 -05:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebd1008215 | ||
|
|
0df958c3a4 | ||
|
|
14709313ae | ||
|
|
ff2dd23918 | ||
|
|
66e74966ee | ||
|
|
9b022d0c61 | ||
|
|
e1b301814e | ||
|
|
2010d8d51e | ||
|
|
22fa977c9b | ||
|
|
285293ec23 | ||
|
|
6497f527ca | ||
|
|
689c167384 | ||
|
|
18a2d76139 | ||
|
|
722bef4257 | ||
|
|
8fe869223d | ||
|
|
1a961aac70 | ||
|
|
81dacef2fe | ||
|
|
92932cd004 | ||
|
|
1f6f8fd720 | ||
|
|
507699924f | ||
|
|
5d1fb2593b | ||
|
|
6012cc1b64 | ||
|
|
36f1b8af9e | ||
|
|
6ac9f6d229 | ||
|
|
3ce4529232 | ||
|
|
d3b8e66b78 | ||
|
|
836623257c | ||
|
|
27f2bc4050 | ||
|
|
5a5f3b1ef0 | ||
|
|
d374c51e93 | ||
|
|
f3a242251b | ||
|
|
3d6f98351f | ||
|
|
970c30f08c | ||
|
|
8556982b17 | ||
|
|
1ab9520c5c | ||
|
|
2a72bde111 | ||
|
|
2385bd75b5 | ||
|
|
53d2e7aaed | ||
|
|
48c13ef400 | ||
|
|
3ab30547eb | ||
|
|
48c4dbf4c4 | ||
|
|
4715bbc2f5 | ||
|
|
1bedbf2191 | ||
|
|
9145d5a476 | ||
|
|
fa6b5aae00 | ||
|
|
753d4a9a67 |
@@ -914,6 +914,51 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "infogulch",
|
||||
"name": "Joe Taber",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/133882?v=4",
|
||||
"profile": "https://github.com/infogulch",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "readingsnail",
|
||||
"name": "Woosuk Park",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1904967?v=4",
|
||||
"profile": "https://www.readingsnail.pe.kr",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dmurph",
|
||||
"name": "Daniel Murphy",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/294026?v=4",
|
||||
"profile": "http://www.dmurph.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Dominic-DallOsto",
|
||||
"name": "Dominic D",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/26859884?v=4",
|
||||
"profile": "https://github.com/Dominic-DallOsto",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "elgirafo",
|
||||
"name": "luca",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/80516439?v=4",
|
||||
"profile": "http://elgirafo.xyz",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
265
docs/index.md
265
docs/index.md
@@ -106,135 +106,142 @@ If that sounds like something you're interested in, I'd love to have you along o
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt=""/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://leonh.fr/"><img src="https://avatars.githubusercontent.com/u/19996318?v=4?s=60" width="60px;" alt=""/><br /><sub><b>léon h</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=leonhfr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nygaard.site"><img src="https://avatars.githubusercontent.com/u/4606342?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nikhil Nygaard</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=njnygaard" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nitwit.se"><img src="https://avatars.githubusercontent.com/u/1382124?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mark Dixon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nitwit-se" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/joeltjames"><img src="https://avatars.githubusercontent.com/u/3732400?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joel James</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joeltjames" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.ryo33.com"><img src="https://avatars.githubusercontent.com/u/8780513?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hashiguchi Ryo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ryo33" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://movermeyer.com"><img src="https://avatars.githubusercontent.com/u/1459385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Overmeyer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=movermeyer" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/derrickqin"><img src="https://avatars.githubusercontent.com/u/3038111?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Derrick Qin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=derrickqin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/zomars/"><img src="https://avatars.githubusercontent.com/u/3504472?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Omar López</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=zomars" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://robincn.com"><img src="https://avatars.githubusercontent.com/u/1583193?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Robin King</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=RobinKing" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/deegovee"><img src="https://avatars.githubusercontent.com/u/4730170?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dheepak </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dheepakg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Pearcekieser"><img src="https://avatars.githubusercontent.com/u/5055971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Pearcekieser</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Pearcekieser" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theowenyoung"><img src="https://avatars.githubusercontent.com/u/62473795?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Owen Young</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=theowenyoung" title="Documentation">📖</a> <a href="#content-theowenyoung" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt=""/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt=""/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iam-yan"><img src="https://avatars.githubusercontent.com/u/48427014?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yan</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=iam-yan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://WikiEducator.org/User:JimTittsler"><img src="https://avatars.githubusercontent.com/u/180326?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jim Tittsler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://malcolmmielle.wordpress.com/"><img src="https://avatars.githubusercontent.com/u/4457840?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Malcolm Mielle</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MalcolmMielle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://snippets.page/"><img src="https://avatars.githubusercontent.com/u/74916913?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Veesar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=veesar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bentongxyz"><img src="https://avatars.githubusercontent.com/u/60358804?v=4?s=60" width="60px;" alt=""/><br /><sub><b>bentongxyz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bentongxyz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianjdevries.com"><img src="https://avatars.githubusercontent.com/u/42778030?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian DeVries</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=techCarpenter" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.drewtyler.com"><img src="https://avatars.githubusercontent.com/u/5640816?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Drew Tyler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=drewtyler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Lauviah0622"><img src="https://avatars.githubusercontent.com/u/43416399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Lauviah0622</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lauviah0622" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.elastic.co/elastic-agent"><img src="https://avatars.githubusercontent.com/u/1813008?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Josh Dover</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joshdover" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://phelm.co.uk"><img src="https://avatars.githubusercontent.com/u/4057948?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Phil Helm</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=phelma" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lingyv-li"><img src="https://avatars.githubusercontent.com/u/8937944?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Larry Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lingyv-li" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt="Jani Eväkallio"/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt="Joe Previte"/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt="Riccardo"/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt="Janne Ojanaho"/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt="Paul Shen"/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt="coffenbacher"/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt="Mathieu Dutour"/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt="Michael Hansen"/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt="David Nadlinger"/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt="Fernando"/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt="Juan Gonzalez"/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt="Louie Christie"/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt="Sandro"/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt="Simon Knott"/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt="Steven"/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt="Tim"/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt="Saurav Khdoolia"/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt="Ankit Tiwari"/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt="Ayush Baweja"/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt="TaiChi-IO"/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt="Juan F Gonzalez "/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt="Sanket Dasgupta"/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt="Nicholas Stafie"/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt="Francis Hamel"/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt="digiguru"/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt="CHIRAG SINGHAL"/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt="Jonathan Carter"/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt="Julian Elve"/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt="Thomas Koppelaar"/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt="Akshay"/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt="John Lindquist"/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt="Ashwin Ramaswami"/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt="Claudio Canales"/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt="vitaly-pevgonen"/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt="Dmitry Shemetov"/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt="hooncp"/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt="Martin Laws"/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt="Sean K Smith"/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt="Kevin Neely"/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt="Arief Rahmansyah"/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt="Vishesh Handa"/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt="Hitesh Kumar"/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt="Spencer Woo"/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt="ingalless"/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt="José Duarte"/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt="Yenly"/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt="hikerpig"/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt="Sigfried Gold"/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt="Tristan Sokol"/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt="Danil Rodin"/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt="Scott Williams"/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt="jackiexiao"/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt="John B Nelson"/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt="Asif Mehedi"/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt="Tan Li"/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt="Shauna Gordon"/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt="Mike Cluck"/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt="Brandon Pugh"/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt="Max Davitt"/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt="Brian Anglin"/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt="elswork"/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://leonh.fr/"><img src="https://avatars.githubusercontent.com/u/19996318?v=4?s=60" width="60px;" alt="léon h"/><br /><sub><b>léon h</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=leonhfr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nygaard.site"><img src="https://avatars.githubusercontent.com/u/4606342?v=4?s=60" width="60px;" alt="Nikhil Nygaard"/><br /><sub><b>Nikhil Nygaard</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=njnygaard" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nitwit.se"><img src="https://avatars.githubusercontent.com/u/1382124?v=4?s=60" width="60px;" alt="Mark Dixon"/><br /><sub><b>Mark Dixon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nitwit-se" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/joeltjames"><img src="https://avatars.githubusercontent.com/u/3732400?v=4?s=60" width="60px;" alt="Joel James"/><br /><sub><b>Joel James</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joeltjames" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.ryo33.com"><img src="https://avatars.githubusercontent.com/u/8780513?v=4?s=60" width="60px;" alt="Hashiguchi Ryo"/><br /><sub><b>Hashiguchi Ryo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ryo33" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://movermeyer.com"><img src="https://avatars.githubusercontent.com/u/1459385?v=4?s=60" width="60px;" alt="Michael Overmeyer"/><br /><sub><b>Michael Overmeyer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=movermeyer" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/derrickqin"><img src="https://avatars.githubusercontent.com/u/3038111?v=4?s=60" width="60px;" alt="Derrick Qin"/><br /><sub><b>Derrick Qin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=derrickqin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/zomars/"><img src="https://avatars.githubusercontent.com/u/3504472?v=4?s=60" width="60px;" alt="Omar López"/><br /><sub><b>Omar López</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=zomars" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://robincn.com"><img src="https://avatars.githubusercontent.com/u/1583193?v=4?s=60" width="60px;" alt="Robin King"/><br /><sub><b>Robin King</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=RobinKing" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/deegovee"><img src="https://avatars.githubusercontent.com/u/4730170?v=4?s=60" width="60px;" alt="Dheepak "/><br /><sub><b>Dheepak </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dheepakg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt="Daniel VG"/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt="Barabas"/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt="Engincan VESKE"/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt="Paul de Raaij"/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt="Scott Bronson"/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt="Rafael Riedel"/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Pearcekieser"><img src="https://avatars.githubusercontent.com/u/5055971?v=4?s=60" width="60px;" alt="Pearcekieser"/><br /><sub><b>Pearcekieser</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Pearcekieser" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theowenyoung"><img src="https://avatars.githubusercontent.com/u/62473795?v=4?s=60" width="60px;" alt="Owen Young"/><br /><sub><b>Owen Young</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=theowenyoung" title="Documentation">📖</a> <a href="#content-theowenyoung" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt="Prashanth Subrahmanyam"/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt="Jonas SPRENGER"/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt="Paul"/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt="memeplex"/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt="AndreiD049"/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iam-yan"><img src="https://avatars.githubusercontent.com/u/48427014?v=4?s=60" width="60px;" alt="Yan"/><br /><sub><b>Yan</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=iam-yan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://WikiEducator.org/User:JimTittsler"><img src="https://avatars.githubusercontent.com/u/180326?v=4?s=60" width="60px;" alt="Jim Tittsler"/><br /><sub><b>Jim Tittsler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://malcolmmielle.wordpress.com/"><img src="https://avatars.githubusercontent.com/u/4457840?v=4?s=60" width="60px;" alt="Malcolm Mielle"/><br /><sub><b>Malcolm Mielle</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MalcolmMielle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://snippets.page/"><img src="https://avatars.githubusercontent.com/u/74916913?v=4?s=60" width="60px;" alt="Veesar"/><br /><sub><b>Veesar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=veesar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bentongxyz"><img src="https://avatars.githubusercontent.com/u/60358804?v=4?s=60" width="60px;" alt="bentongxyz"/><br /><sub><b>bentongxyz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bentongxyz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianjdevries.com"><img src="https://avatars.githubusercontent.com/u/42778030?v=4?s=60" width="60px;" alt="Brian DeVries"/><br /><sub><b>Brian DeVries</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=techCarpenter" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt="Clifford Fajardo "/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt="Chris Usick"/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt="Joe DeCock"/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.drewtyler.com"><img src="https://avatars.githubusercontent.com/u/5640816?v=4?s=60" width="60px;" alt="Drew Tyler"/><br /><sub><b>Drew Tyler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=drewtyler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Lauviah0622"><img src="https://avatars.githubusercontent.com/u/43416399?v=4?s=60" width="60px;" alt="Lauviah0622"/><br /><sub><b>Lauviah0622</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lauviah0622" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.elastic.co/elastic-agent"><img src="https://avatars.githubusercontent.com/u/1813008?v=4?s=60" width="60px;" alt="Josh Dover"/><br /><sub><b>Josh Dover</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joshdover" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://phelm.co.uk"><img src="https://avatars.githubusercontent.com/u/4057948?v=4?s=60" width="60px;" alt="Phil Helm"/><br /><sub><b>Phil Helm</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=phelma" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lingyv-li"><img src="https://avatars.githubusercontent.com/u/8937944?v=4?s=60" width="60px;" alt="Larry Li"/><br /><sub><b>Larry Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lingyv-li" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/infogulch"><img src="https://avatars.githubusercontent.com/u/133882?v=4?s=60" width="60px;" alt="Joe Taber"/><br /><sub><b>Joe Taber</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=infogulch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.readingsnail.pe.kr"><img src="https://avatars.githubusercontent.com/u/1904967?v=4?s=60" width="60px;" alt="Woosuk Park"/><br /><sub><b>Woosuk Park</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=readingsnail" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.dmurph.com"><img src="https://avatars.githubusercontent.com/u/294026?v=4?s=60" width="60px;" alt="Daniel Murphy"/><br /><sub><b>Daniel Murphy</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dmurph" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dominic-DallOsto"><img src="https://avatars.githubusercontent.com/u/26859884?v=4?s=60" width="60px;" alt="Dominic D"/><br /><sub><b>Dominic D</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Dominic-DallOsto" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://elgirafo.xyz"><img src="https://avatars.githubusercontent.com/u/80516439?v=4?s=60" width="60px;" alt="luca"/><br /><sub><b>luca</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elgirafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
45
docs/user/features/commands.md
Normal file
45
docs/user/features/commands.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Foam Commands
|
||||
|
||||
Foam has various commands that you can explore by calling the command palette and typing "Foam".
|
||||
|
||||
In particular, some commands can be very customizible and can help with custom workflows and use cases.
|
||||
|
||||
## foam-vscode.create-note command
|
||||
|
||||
This command creates a note.
|
||||
Although it works fine on its own, it can be customized to achieve various use cases.
|
||||
Here are the settings available for the command:
|
||||
- notePath: The path of the note to create. If relative it will be resolved against the workspace root.
|
||||
- templatePath: The path of the template to use. If relative it will be resolved against the workspace root.
|
||||
- text: The text to use for the note. If also a template is provided, the template has precedence
|
||||
- variables: Variables to use in the text or template (e.g. `FOAM_TITLE`)
|
||||
- date: The date used to resolve the FOAM_DATE_* variables. in `YYYY-MM-DD` format
|
||||
- onFileExists?: 'overwrite' | 'open' | 'ask' | 'cancel': What to do in case the target file already exists
|
||||
|
||||
To customize a command and associate a key binding to it, open the key binding settings and add the appropriate configuration, here are some examples:
|
||||
|
||||
- Create a note called `test note.md` with some text. If the note already exists, ask for a new name
|
||||
```
|
||||
{
|
||||
"key": "alt+f",
|
||||
"command": "foam-vscode.create-note",
|
||||
"args": {
|
||||
"text": "test note ${FOAM_DATE_YEAR}",
|
||||
"notePath": "test note.md",
|
||||
"onFileExists": "ask"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Create a note following the `weekly-note.md` template. If the note already exists, open it
|
||||
```
|
||||
{
|
||||
"key": "alt+g",
|
||||
"command": "foam-vscode.create-note",
|
||||
"args": {
|
||||
"templatePath": ".foam/templates/weekly-note.md",
|
||||
"onFileExists": "open"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
# Spell Checking
|
||||
|
||||
Foam comes with a spell checker powered by the [Spellright extension](https://marketplace.visualstudio.com/items?itemName=ban.spellright).
|
||||
There are many spell checking extensions for VS Code.
|
||||
|
||||
Misspelled words are highlighted, like hellow.
|
||||
You can place the cursor on top of the word, and press `cmd+.` for suggestions on how to fix the problem.
|
||||
The most popular spell checker for VS Code is [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker).
|
||||
|
||||
You can configure the extension in the settings, for example to:
|
||||
Another one of our favorites is [LTeX](https://marketplace.visualstudio.com/items?itemName=valentjn.vscode-ltex&ssr=false#overview), which is a bit heavier but offers some extra functionality.
|
||||
|
||||
- ignore certain files
|
||||
- change the language(s)
|
||||
- and much more
|
||||
|
||||
You can use any number of alternative spell checking extensions for VS Code.
|
||||
One of our favorites is [LTeX](https://marketplace.visualstudio.com/items?itemName=valentjn.vscode-ltex&ssr=false#overview), which is a bit heavier but offers some extra functionality.
|
||||
Another popular one is [Spellright](https://marketplace.visualstudio.com/items?itemName=ban.spellright), but be mindful that there have been reports of incompatibility with the `vscode-markdown` extension (see https://github.com/foambubble/foam/issues/1068).
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
## I don't want Foam enabled for all my workspaces
|
||||
Any extension you install in Visual Studio Code is enabled by default. Give the philosophy of Foam it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as [documented by Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-marketplace#_manage-extensions)
|
||||
|
||||
## I want to publish the graph view to GitHub pages or Vercel
|
||||
If you want a different front-end look to your published foam and a way to see your graph view, we'd recommend checking out these templates:
|
||||
- [foam-gatsby](https://github.com/mathieudutour/foam-gatsby-template) by [Mathieu Dutour](https://github.com/mathieudutour)
|
||||
- [foam-gatsby-kb](https://github.com/hikerpig/foam-template-gatsby-kb) by [hikerpig](https://github.com/hikerpig)
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[recommended-extensions]: getting-started/recommended-extensions.md "Recommended Extensions"
|
||||
[foam-file-format]: ../dev/foam-file-format.md "Foam File Format"
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
- Write out a new `[[wikilink]]` and `Cmd` + `Click` to create a new file and enter it.
|
||||
- For keyboard navigation, use the 'Follow Definition' key `F12` (or [remap the 'editor.action.revealDefinition' key binding](https://code.visualstudio.com/docs/getstarted/keybindings) to something more ergonomic)
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `Foam: Create New Note` and enter a **Title Case Name** to create `Title Case Name.md`
|
||||
- Add a keyboard binding to make creating new notes easier.
|
||||
- `Cmd` + `Shift` + `P` (`Ctrl` + `Shift` + `P` for Windows), execute `Foam: Create Note` and enter a **Title Case Name** to create `Title Case Name.md`
|
||||
- Add a keyboard binding to make creating new notes easier. See [[commands]] for more info on this.
|
||||
- The [[note-templates]] used by this command can be customized.
|
||||
- You shouldn't worry too much about categorizing your notes. You can always [[search-for-notes]], and explore them using the [[graph-visualization]].
|
||||
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[commands]: ../features/commands.md "Foam Commands"
|
||||
[note-templates]: ../features/note-templates.md "Note Templates"
|
||||
[search-for-notes]: ../recipes/search-for-notes.md "Search for Notes"
|
||||
[graph-visualization]: ../features/graph-visualization.md "Graph Visualization"
|
||||
|
||||
@@ -7,7 +7,6 @@ This list is subject to change.
|
||||
- [Foam for VSCode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) (alpha)
|
||||
- [Markdown All In One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one)
|
||||
- [Paste Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image)
|
||||
- [Spell Right](https://marketplace.visualstudio.com/items?itemName=ban.spellright)
|
||||
|
||||
## Extensions For Additional Features
|
||||
|
||||
@@ -17,6 +16,7 @@ These extensions are not (yet?) defined in `.vscode/extensions.json`, but have b
|
||||
- [Markdown Emoji](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-emoji) (adds `:smile:` syntax, works with emojisense to provide autocomplete for this syntax)
|
||||
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
|
||||
- [Excalidraw whiteboard and sketching tool integration](https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor)
|
||||
- [VSCode PDF Viewing](https://marketplace.visualstudio.com/items?itemName=tomoki1207.pdf)
|
||||
- [Project Manager](https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager) (to quickly switch between projects)
|
||||
- [Markdown Extended](https://marketplace.visualstudio.com/items?itemName=jebbs.markdown-extended) (with `kbd` option disabled, `kbd` turns wikilinks into non-clickable buttons)
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.19.3"
|
||||
"version": "0.20.2"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
out/**/*.test.*
|
||||
out/**/*.spec.*
|
||||
test-data/**
|
||||
src/**
|
||||
jest.config.js
|
||||
.test-workspace
|
||||
.gitignore
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
|
||||
@@ -4,7 +4,54 @@ All notable changes to the "foam-vscode" extension will be documented in this fi
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [0.19.2] - 2022-07-11
|
||||
## [0.20.2] - 2022-10-26
|
||||
|
||||
Fixes and Improvements:
|
||||
- Creating new note uses default template when none is provided (#1094)
|
||||
|
||||
Internal:
|
||||
- Changed matcher implementation to remove dependency on micromatch/glob
|
||||
- Removed unnecessary dependencies and assets from extension
|
||||
|
||||
## [0.20.1] - 2022-10-13
|
||||
|
||||
Fixes and Improvements:
|
||||
- Improved support for daily notes in multi root workspace (#1073)
|
||||
- Create note from placeholder using template (#1061 - thanks @Dominic-DallOsto)
|
||||
- Improved support for globs in multi root workspace (#1083)
|
||||
|
||||
## [0.20.0] - 2022-09-30
|
||||
|
||||
New Features:
|
||||
- Added `foam-vscode.create-note` command, which can be very customized for several use cases (#1076)
|
||||
|
||||
Fixes and Improvements:
|
||||
- Removed `+` as a trigger char for date snippets
|
||||
- Improved attachment support (#915)
|
||||
- Improved error handling when starting Foam without an open workspace (#908)
|
||||
- Added support for opening non-text files via wikilink (#915)
|
||||
- Dataviz: now clicking is enough to open a link from the graph
|
||||
- Dataviz: clicking on images/attachments will open them
|
||||
|
||||
## [0.19.5] - 2022-09-01
|
||||
|
||||
Fixes and Improvements:
|
||||
- Added `FOAM_DATE_WEEK` variable (#1053 - Thanks @dmurph)
|
||||
- Fixed extension inclusion when generating references for attachments
|
||||
- Link completion label can be note title as well as path (#1059)
|
||||
- Images and attachments are not shown by default in graph view (#1056)
|
||||
|
||||
## [0.19.4] - 2022-08-07
|
||||
|
||||
Fixes and Improvements:
|
||||
- Fixed note embed in preview (#1052)
|
||||
|
||||
## [0.19.3] - 2022-08-04
|
||||
|
||||
Fixes and Improvements:
|
||||
- Image embeds fixed in preview (#1036)
|
||||
|
||||
## [0.19.2] - 2022-08-04
|
||||
|
||||
Fixes and Improvements:
|
||||
- Added support for angle markdown links (#1044)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 604 KiB |
@@ -8,7 +8,7 @@
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/foambubble/foam",
|
||||
"version": "0.19.3",
|
||||
"version": "0.20.2",
|
||||
"license": "MIT",
|
||||
"publisher": "foam",
|
||||
"engines": {
|
||||
@@ -29,6 +29,7 @@
|
||||
"onCommand:foam-vscode.copy-without-brackets",
|
||||
"onCommand:foam-vscode.show-graph",
|
||||
"onCommand:foam-vscode.create-new-template",
|
||||
"onCommand:foam-vscode.create-note",
|
||||
"onCommand:foam-vscode.create-note-from-template",
|
||||
"onCommand:foam-vscode.create-note-from-default-template"
|
||||
],
|
||||
@@ -128,6 +129,10 @@
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "foam-vscode.create-note-from-default-template",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.update-graph",
|
||||
"when": "false"
|
||||
@@ -159,6 +164,10 @@
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "foam-vscode.create-note",
|
||||
"title": "Foam: Create New Note"
|
||||
},
|
||||
{
|
||||
"command": "foam-vscode.clear-cache",
|
||||
"title": "Foam: Clear Cache"
|
||||
@@ -243,6 +252,34 @@
|
||||
"configuration": {
|
||||
"title": "Foam",
|
||||
"properties": {
|
||||
"foam.completion.label": {
|
||||
"type": "string",
|
||||
"default": "path",
|
||||
"description": "Describes what note property to use as a label for completion items",
|
||||
"enum": [
|
||||
"path",
|
||||
"title",
|
||||
"identifier"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Use the path of the note",
|
||||
"Use the title of the note",
|
||||
"Use the identifier of the note"
|
||||
]
|
||||
},
|
||||
"foam.completion.useAlias": {
|
||||
"type": "string",
|
||||
"default": "never",
|
||||
"description": "Specifies in which cases to use an alias when creating a wikilink",
|
||||
"enum": [
|
||||
"never",
|
||||
"whenPathDiffersFromTitle"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Never use aliases in completion items",
|
||||
"Use alias if resource path is different from title"
|
||||
]
|
||||
},
|
||||
"foam.files.ignore": {
|
||||
"type": [
|
||||
"array"
|
||||
@@ -255,6 +292,11 @@
|
||||
],
|
||||
"description": "Specifies the list of globs that will be ignored by Foam (e.g. they will not be considered when creating the graph). To ignore the all the content of a given folder, use `<folderName>/**/*`"
|
||||
},
|
||||
"foam.files.attachmentExtensions": {
|
||||
"type": "string",
|
||||
"default": "pdf mp3 webm wav m4a mp4 avi mov rtf txt doc docx pages xls xlsx numbers ppt pptm pptx",
|
||||
"description": "Space separated list of file extensions that will be considered attachments"
|
||||
},
|
||||
"foam.logging.level": {
|
||||
"type": "string",
|
||||
"default": "info",
|
||||
@@ -443,10 +485,12 @@
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jest": "^25.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.2.2",
|
||||
"jest-extended": "^0.11.5",
|
||||
"markdown-it": "^12.0.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"tsdx": "^0.13.2",
|
||||
@@ -458,19 +502,14 @@
|
||||
"dependencies": {
|
||||
"dateformat": "^3.0.3",
|
||||
"detect-newline": "^3.1.0",
|
||||
"fast-array-diff": "^1.0.1",
|
||||
"github-slugger": "^1.4.0",
|
||||
"glob": "^7.1.6",
|
||||
"gray-matter": "^4.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^7.12.0",
|
||||
"markdown-it-regex": "^0.2.0",
|
||||
"micromatch": "^4.0.2",
|
||||
"remark-frontmatter": "^2.0.0",
|
||||
"remark-parse": "^8.0.2",
|
||||
"remark-wiki-link": "^0.0.4",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"replace-ext": "^2.0.0",
|
||||
"title-case": "^3.0.2",
|
||||
"unified": "^9.0.0",
|
||||
"unist-util-visit": "^2.0.2",
|
||||
|
||||
@@ -4,10 +4,10 @@ import { MarkdownResourceProvider } from '../services/markdown-provider';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { FileDataStore, Matcher } from '../services/datastore';
|
||||
import { Logger } from '../utils/log';
|
||||
import detectNewline from 'detect-newline';
|
||||
import { createMarkdownParser } from '../services/markdown-parser';
|
||||
import { FileDataStore } from '../../test/test-datastore';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -20,11 +20,13 @@ describe('generateHeadings', () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
|
||||
const dataStore = new FileDataStore(readFileFromFs);
|
||||
const dataStore = new FileDataStore(
|
||||
readFileFromFs,
|
||||
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
|
||||
);
|
||||
const parser = createMarkdownParser();
|
||||
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
|
||||
_workspace = await FoamWorkspace.fromProviders([mdProvider]);
|
||||
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
|
||||
});
|
||||
|
||||
it.skip('should add heading to a file that does not have them', async () => {
|
||||
|
||||
@@ -4,12 +4,12 @@ import { MarkdownResourceProvider } from '../services/markdown-provider';
|
||||
import { Resource } from '../model/note';
|
||||
import { Range } from '../model/range';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { FileDataStore, Matcher } from '../services/datastore';
|
||||
import { Logger } from '../utils/log';
|
||||
import fs from 'fs';
|
||||
import { URI } from '../model/uri';
|
||||
import { EOL } from 'os';
|
||||
import { createMarkdownParser } from '../services/markdown-parser';
|
||||
import { FileDataStore } from '../../test/test-datastore';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -23,14 +23,16 @@ describe('generateLinkReferences', () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
|
||||
/** Use fs for reading files in units where vscode.workspace is unavailable */
|
||||
const readFile = async (uri: URI) =>
|
||||
(await fs.promises.readFile(uri.toFsPath())).toString();
|
||||
const dataStore = new FileDataStore(readFile);
|
||||
const dataStore = new FileDataStore(
|
||||
readFile,
|
||||
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
|
||||
);
|
||||
const parser = createMarkdownParser();
|
||||
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
|
||||
_workspace = await FoamWorkspace.fromProviders([mdProvider]);
|
||||
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
|
||||
});
|
||||
|
||||
it('initialised test graph correctly', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore, IMatcher } from '../services/datastore';
|
||||
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
|
||||
import { FoamWorkspace } from './workspace';
|
||||
import { FoamGraph } from './graph';
|
||||
import { ResourceParser } from './note';
|
||||
@@ -22,14 +22,18 @@ export interface Foam extends IDisposable {
|
||||
|
||||
export const bootstrap = async (
|
||||
matcher: IMatcher,
|
||||
watcher: IWatcher | undefined,
|
||||
dataStore: IDataStore,
|
||||
parser: ResourceParser,
|
||||
initialProviders: ResourceProvider[]
|
||||
) => {
|
||||
const workspace = new FoamWorkspace();
|
||||
const tsStart = Date.now();
|
||||
|
||||
await Promise.all(initialProviders.map(p => workspace.registerProvider(p)));
|
||||
const workspace = await FoamWorkspace.fromProviders(
|
||||
initialProviders,
|
||||
dataStore
|
||||
);
|
||||
|
||||
const tsWsDone = Date.now();
|
||||
Logger.info(`Workspace loaded in ${tsWsDone - tsStart}ms`);
|
||||
|
||||
@@ -41,13 +45,28 @@ export const bootstrap = async (
|
||||
const tsTagsEnd = Date.now();
|
||||
Logger.info(`Tags loaded in ${tsTagsEnd - tsGraphDone}ms`);
|
||||
|
||||
watcher?.onDidChange(async uri => {
|
||||
if (matcher.isMatch(uri)) {
|
||||
await workspace.fetchAndSet(uri);
|
||||
}
|
||||
});
|
||||
watcher?.onDidCreate(async uri => {
|
||||
await matcher.refresh();
|
||||
if (matcher.isMatch(uri)) {
|
||||
await workspace.fetchAndSet(uri);
|
||||
}
|
||||
});
|
||||
watcher?.onDidDelete(uri => {
|
||||
workspace.delete(uri);
|
||||
});
|
||||
|
||||
const foam: Foam = {
|
||||
workspace,
|
||||
graph,
|
||||
tags,
|
||||
services: {
|
||||
dataStore,
|
||||
parser,
|
||||
dataStore,
|
||||
matcher,
|
||||
},
|
||||
dispose: () => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { URI } from './uri';
|
||||
import { FoamWorkspace } from './workspace';
|
||||
|
||||
export interface ResourceProvider extends IDisposable {
|
||||
init: (workspace: FoamWorkspace) => Promise<void>;
|
||||
supports: (uri: URI) => boolean;
|
||||
readAsMarkdown: (uri: URI) => Promise<string | null>;
|
||||
fetch: (uri: URI) => Promise<Resource | null>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from './uri';
|
||||
import { asAbsoluteUri, URI } from './uri';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -81,3 +81,47 @@ describe('Foam URI', () => {
|
||||
).toEqual(URI.file('../a/note.md'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('asAbsoluteUri', () => {
|
||||
it('should throw if no workspace folder is found', () => {
|
||||
expect(() => asAbsoluteUri(URI.file('relative/path'), [])).toThrow();
|
||||
});
|
||||
it('should return the given URI if already absolute', () => {
|
||||
const uri = URI.file('/absolute/path');
|
||||
expect(asAbsoluteUri(uri, [URI.file('/base')])).toEqual(uri);
|
||||
});
|
||||
describe('with relative URI', () => {
|
||||
it('should return a URI relative if the given URI is relative and there is only one workspace folder', () => {
|
||||
const uri = URI.file('relative/path');
|
||||
const workspaceFolder = URI.file('/workspace/folder');
|
||||
expect(asAbsoluteUri(uri, [workspaceFolder])).toEqual(
|
||||
workspaceFolder.joinPath(uri.path)
|
||||
);
|
||||
});
|
||||
it('should match the first folder with the same name as the first part of the URI', () => {
|
||||
const uri = URI.file('folder2/file');
|
||||
const workspaceFolder1 = URI.file('/absolute/path/folder1');
|
||||
const workspaceFolder2 = URI.file('/absolute/path/folder2');
|
||||
expect(asAbsoluteUri(uri, [workspaceFolder1, workspaceFolder2])).toEqual(
|
||||
workspaceFolder2.joinPath('file')
|
||||
);
|
||||
});
|
||||
});
|
||||
it('should use the first folder if no matching folder is found', () => {
|
||||
const uri = URI.file('folder3/file');
|
||||
const workspaceFolder1 = URI.file('/absolute/path/folder1');
|
||||
const workspaceFolder2 = URI.file('/absolute/path/folder2');
|
||||
expect(asAbsoluteUri(uri, [workspaceFolder1, workspaceFolder2])).toEqual(
|
||||
workspaceFolder1.joinPath(uri.path)
|
||||
);
|
||||
});
|
||||
it('should use the first matching folder', () => {
|
||||
const uri = URI.file('folder/file');
|
||||
const workspaceFolder1 = URI.file('/absolute/path1');
|
||||
const workspaceFolder2 = URI.file('/absolute/path2/folder');
|
||||
const workspaceFolder3 = URI.file('/absolute/path3/folder');
|
||||
expect(
|
||||
asAbsoluteUri(uri, [workspaceFolder1, workspaceFolder2, workspaceFolder3])
|
||||
).toEqual(workspaceFolder2.joinPath('file'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -367,3 +367,24 @@ function encodeURIComponentMinimal(path: string): string {
|
||||
}
|
||||
return res !== undefined ? res : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a relative URI into an absolute URI given a collection of base folders.
|
||||
* In case of multiple matches it returns the first one.
|
||||
*
|
||||
* @see {@link pathUtils.asAbsolutePaths|path.asAbsolutePath}
|
||||
*
|
||||
* @param uri the uri to evaluate
|
||||
* @param baseFolders the base folders to use
|
||||
* @returns an absolute uri
|
||||
*
|
||||
* TODO this probably needs to be moved to the workspace service
|
||||
*/
|
||||
export function asAbsoluteUri(uri: URI, baseFolders: URI[]): URI {
|
||||
return URI.file(
|
||||
pathUtils.asAbsolutePaths(
|
||||
uri.path,
|
||||
baseFolders.map(f => f.path)
|
||||
)[0]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { isSome } from '../utils';
|
||||
import { Emitter } from '../common/event';
|
||||
import { ResourceProvider } from './provider';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { IDataStore } from '../services/datastore';
|
||||
|
||||
export class FoamWorkspace implements IDisposable {
|
||||
private onDidAddEmitter = new Emitter<Resource>();
|
||||
@@ -23,7 +24,6 @@ export class FoamWorkspace implements IDisposable {
|
||||
|
||||
registerProvider(provider: ResourceProvider) {
|
||||
this.providers.push(provider);
|
||||
return provider.init(this);
|
||||
}
|
||||
|
||||
set(resource: Resource) {
|
||||
@@ -159,6 +159,20 @@ export class FoamWorkspace implements IDisposable {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a resource URI, and adds it to the workspace as a resource.
|
||||
* If the URI is not supported by any provider or is not found, it will not
|
||||
* add anything to the workspace, and return null.
|
||||
*
|
||||
* @param uri the URI where the resource is located
|
||||
* @returns A promise to the Resource, or null if none was found
|
||||
*/
|
||||
public async fetchAndSet(uri: URI): Promise<Resource | null> {
|
||||
const resource = await this.fetch(uri);
|
||||
resource && this.set(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
public readAsMarkdown(uri: URI): Promise<string | null> {
|
||||
for (const provider of this.providers) {
|
||||
if (provider.supports(uri)) {
|
||||
@@ -220,12 +234,13 @@ export class FoamWorkspace implements IDisposable {
|
||||
}
|
||||
|
||||
static async fromProviders(
|
||||
providers: ResourceProvider[]
|
||||
providers: ResourceProvider[],
|
||||
dataStore: IDataStore
|
||||
): Promise<FoamWorkspace> {
|
||||
const workspace = new FoamWorkspace();
|
||||
for (const provider of providers) {
|
||||
await workspace.registerProvider(provider);
|
||||
}
|
||||
await Promise.all(providers.map(p => workspace.registerProvider(p)));
|
||||
const files = await dataStore.list();
|
||||
await Promise.all(files.map(f => workspace.fetchAndSet(f)));
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Resource, ResourceLink } from '../model/note';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../model/uri';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { ResourceProvider } from '../model/provider';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
|
||||
const imageExtensions = ['.png', '.jpg', '.gif'];
|
||||
const attachmentExtensions = ['.pdf', ...imageExtensions];
|
||||
const attachmentExtConfig = getFoamVsCodeConfig(
|
||||
'files.attachmentExtensions',
|
||||
''
|
||||
)
|
||||
.split(' ')
|
||||
.map(ext => '.' + ext.trim());
|
||||
|
||||
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'];
|
||||
const attachmentExtensions = [...attachmentExtConfig, ...imageExtensions];
|
||||
|
||||
const asResource = (uri: URI): Resource => {
|
||||
const type = imageExtensions.includes(uri.getExtension())
|
||||
@@ -29,48 +35,10 @@ const asResource = (uri: URI): Resource => {
|
||||
export class AttachmentResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly matcher: IMatcher,
|
||||
private readonly dataStore: IDataStore,
|
||||
private readonly watcher?: IWatcher
|
||||
) {}
|
||||
|
||||
async init(workspace: FoamWorkspace) {
|
||||
const filesByFolder = await Promise.all(
|
||||
this.matcher.include.map(glob =>
|
||||
this.dataStore.list(glob, this.matcher.exclude)
|
||||
)
|
||||
);
|
||||
const files = this.matcher
|
||||
.match(filesByFolder.flat())
|
||||
.filter(this.supports);
|
||||
|
||||
for (const uri of files) {
|
||||
Logger.debug('Found: ' + uri.toString());
|
||||
workspace.set(asResource(uri));
|
||||
}
|
||||
|
||||
if (this.watcher != null) {
|
||||
this.disposables = [
|
||||
this.watcher.onDidChange(async uri => {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
workspace.set(asResource(uri));
|
||||
}
|
||||
}),
|
||||
this.watcher.onDidCreate(async uri => {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
workspace.set(asResource(uri));
|
||||
}
|
||||
}),
|
||||
this.watcher.onDidDelete(uri => {
|
||||
this.supports(uri) && workspace.delete(uri);
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
supports(uri: URI) {
|
||||
return attachmentExtensions.includes(uri.getExtension());
|
||||
return attachmentExtensions.includes(
|
||||
uri.getExtension().toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
async readAsMarkdown(uri: URI): Promise<string | null> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileFromFs, TEST_DATA_DIR } from '../../test/test-utils';
|
||||
import { Matcher, toMatcherPathFormat } from '../../test/test-datastore';
|
||||
import { TEST_DATA_DIR } from '../../test/test-utils';
|
||||
import { URI } from '../model/uri';
|
||||
import { Logger } from '../utils/log';
|
||||
import { FileDataStore, Matcher, toMatcherPathFormat } from './datastore';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
@@ -62,11 +62,11 @@ describe('Matcher', () => {
|
||||
});
|
||||
|
||||
it('happy path', () => {
|
||||
const matcher = new Matcher([URI.file('/')], ['**/*'], ['**/*.pdf']);
|
||||
expect(matcher.isMatch(URI.file('/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/file.pdf'))).toBeFalsy();
|
||||
expect(matcher.isMatch(URI.file('/dir/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/dir/file.pdf'))).toBeFalsy();
|
||||
const matcher = new Matcher([URI.file('/root/')], ['**/*'], ['**/*.pdf']);
|
||||
expect(matcher.isMatch(URI.file('/root/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/root/file.pdf'))).toBeFalsy();
|
||||
expect(matcher.isMatch(URI.file('/root/dir/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/root/dir/file.pdf'))).toBeFalsy();
|
||||
});
|
||||
|
||||
it('ignores files in the exclude list', () => {
|
||||
@@ -83,11 +83,3 @@ describe('Matcher', () => {
|
||||
expect(matcher.isMatch(files[3])).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Datastore', () => {
|
||||
it('uses the matcher to get the file list', async () => {
|
||||
const matcher = new Matcher([testFolder], ['**/*.md'], []);
|
||||
const ds = new FileDataStore(readFileFromFs);
|
||||
expect((await ds.list(matcher.include[0])).length).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import micromatch from 'micromatch';
|
||||
import { URI } from '../model/uri';
|
||||
import { Logger } from '../utils/log';
|
||||
import { glob } from 'glob';
|
||||
import { promisify } from 'util';
|
||||
import { isWindows } from '../common/platform';
|
||||
import { Event } from '../common/event';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
/**
|
||||
* Represents a source of files and content
|
||||
*/
|
||||
export interface IDataStore {
|
||||
/**
|
||||
* List the files matching the given glob from the
|
||||
* store
|
||||
*/
|
||||
list: () => Promise<URI[]>;
|
||||
|
||||
/**
|
||||
* Read the content of the file from the store
|
||||
*
|
||||
* Returns `null` in case of errors while reading
|
||||
*/
|
||||
read: (uri: URI) => Promise<string | null>;
|
||||
}
|
||||
|
||||
export interface IWatcher {
|
||||
onDidChange: Event<URI>;
|
||||
onDidCreate: Event<URI>;
|
||||
onDidDelete: Event<URI>;
|
||||
}
|
||||
|
||||
export interface IMatcher {
|
||||
/**
|
||||
@@ -24,6 +42,14 @@ export interface IMatcher {
|
||||
*/
|
||||
isMatch(uri: URI): boolean;
|
||||
|
||||
/**
|
||||
* Refreshes the list of files that this matcher matches
|
||||
* To be used when new files are added to the workspace,
|
||||
* it can be a more or less expensive operation depending on the
|
||||
* implementation of the matcher
|
||||
*/
|
||||
refresh(): Promise<void>;
|
||||
|
||||
/**
|
||||
* The include globs
|
||||
*/
|
||||
@@ -35,100 +61,14 @@ export interface IMatcher {
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The matcher requires the path to be in unix format, so if we are in windows
|
||||
* we convert the fs path on the way in and out
|
||||
*/
|
||||
export const toMatcherPathFormat = isWindows
|
||||
? (uri: URI) => uri.toFsPath().replace(/\\/g, '/')
|
||||
: (uri: URI) => uri.toFsPath();
|
||||
|
||||
export const toFsPath = isWindows
|
||||
? (path: string): string => path.replace(/\//g, '\\')
|
||||
: (path: string): string => path;
|
||||
|
||||
export class Matcher implements IMatcher {
|
||||
public readonly folders: string[];
|
||||
public readonly include: string[] = [];
|
||||
public readonly exclude: string[] = [];
|
||||
|
||||
export class GenericDataStore implements IDataStore {
|
||||
constructor(
|
||||
baseFolders: URI[],
|
||||
include: string[] = ['**/*'],
|
||||
exclude: string[] = []
|
||||
) {
|
||||
this.folders = baseFolders.map(toMatcherPathFormat);
|
||||
Logger.info('Workspace folders: ', this.folders);
|
||||
private readonly listFiles: () => Promise<URI[]>,
|
||||
private readFile: (uri: URI) => Promise<string>
|
||||
) {}
|
||||
|
||||
this.folders.forEach(folder => {
|
||||
const withFolder = folderPlusGlob(folder);
|
||||
this.include.push(
|
||||
...include.map(glob => {
|
||||
return withFolder(glob);
|
||||
})
|
||||
);
|
||||
this.exclude.push(...exclude.map(withFolder));
|
||||
});
|
||||
Logger.info('Glob patterns', {
|
||||
includeGlobs: this.include,
|
||||
ignoreGlobs: this.exclude,
|
||||
});
|
||||
}
|
||||
|
||||
match(files: URI[]) {
|
||||
const matches = micromatch(
|
||||
files.map(f => f.toFsPath()),
|
||||
this.include,
|
||||
{
|
||||
ignore: this.exclude,
|
||||
nocase: true,
|
||||
format: toFsPath,
|
||||
}
|
||||
);
|
||||
return matches.map(URI.file);
|
||||
}
|
||||
|
||||
isMatch(uri: URI) {
|
||||
return this.match([uri]).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IWatcher {
|
||||
onDidChange: Event<URI>;
|
||||
onDidCreate: Event<URI>;
|
||||
onDidDelete: Event<URI>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a source of files and content
|
||||
*/
|
||||
export interface IDataStore {
|
||||
/**
|
||||
* List the files matching the given glob from the
|
||||
* store
|
||||
*/
|
||||
list: (glob: string, ignoreGlob?: string | string[]) => Promise<URI[]>;
|
||||
|
||||
/**
|
||||
* Read the content of the file from the store
|
||||
*
|
||||
* Returns `null` in case of errors while reading
|
||||
*/
|
||||
read: (uri: URI) => Promise<string | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* File system based data store
|
||||
*/
|
||||
export class FileDataStore implements IDataStore {
|
||||
constructor(private readFile: (uri: URI) => Promise<string>) {}
|
||||
|
||||
async list(glob: string, ignoreGlob?: string | string[]): Promise<URI[]> {
|
||||
const res = await findAllFiles(glob, {
|
||||
ignore: ignoreGlob,
|
||||
strict: false,
|
||||
});
|
||||
return res.map(URI.file);
|
||||
async list(): Promise<URI[]> {
|
||||
return this.listFiles();
|
||||
}
|
||||
|
||||
async read(uri: URI) {
|
||||
@@ -143,12 +83,74 @@ export class FileDataStore implements IDataStore {
|
||||
}
|
||||
}
|
||||
|
||||
export const folderPlusGlob = (folder: string) => (glob: string): string => {
|
||||
if (folder.substr(-1) === '/') {
|
||||
folder = folder.slice(0, -1);
|
||||
/**
|
||||
* A matcher that instead of using globs uses a list of files to
|
||||
* check the matches.
|
||||
* The {@link refresh} function has been added to the interface to accomodate
|
||||
* this matcher, far from ideal but to be refactored later
|
||||
*/
|
||||
export class FileListBasedMatcher implements IMatcher {
|
||||
private files: string[] = [];
|
||||
include: string[];
|
||||
exclude: string[];
|
||||
|
||||
constructor(files: URI[], private readonly listFiles: () => Promise<URI[]>) {
|
||||
this.files = files.map(f => f.path);
|
||||
}
|
||||
if (glob.startsWith('/')) {
|
||||
glob = glob.slice(1);
|
||||
|
||||
match(files: URI[]): URI[] {
|
||||
return files.filter(f => this.files.includes(f.path));
|
||||
}
|
||||
return folder.length > 0 ? `${folder}/${glob}` : glob;
|
||||
};
|
||||
|
||||
isMatch(uri: URI): boolean {
|
||||
return this.files.includes(uri.path);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
this.files = (await this.listFiles()).map(f => f.path);
|
||||
}
|
||||
|
||||
static async createFromListFn(listFiles: () => Promise<URI[]>) {
|
||||
const files = await listFiles();
|
||||
return new FileListBasedMatcher(files, listFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A matcher that includes all URIs passed to it
|
||||
*/
|
||||
export class AlwaysIncludeMatcher implements IMatcher {
|
||||
include: string[] = ['**/*'];
|
||||
exclude: string[] = [];
|
||||
match(files: URI[]): URI[] {
|
||||
return files;
|
||||
}
|
||||
|
||||
isMatch(uri: URI): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
refresh(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export class SubstringExcludeMatcher implements IMatcher {
|
||||
include: string[] = ['**/*'];
|
||||
exclude: string[] = [];
|
||||
constructor(exclude: string) {
|
||||
this.exclude = [exclude];
|
||||
}
|
||||
|
||||
match(files: URI[]): URI[] {
|
||||
return files.filter(f => this.isMatch(f));
|
||||
}
|
||||
|
||||
isMatch(uri: URI): boolean {
|
||||
return !uri.path.includes(this.exclude[0]);
|
||||
}
|
||||
|
||||
refresh(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,21 @@ describe('Generation of markdown references', () => {
|
||||
expect(references.map(r => r.url)).toEqual(['page-b.md', 'page-c.md']);
|
||||
});
|
||||
|
||||
it('should always add extensions for attachments, even when includeExtension = false', () => {
|
||||
const workspace = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
'Link to [[page-b]] and [[image.png]]',
|
||||
'/dir1/page-a.md'
|
||||
);
|
||||
workspace
|
||||
.set(noteA)
|
||||
.set(createNoteFromMarkdown('Content of note B', '/dir1/page-b.md'))
|
||||
.set(createNoteFromMarkdown('', '/dir1/image.png'));
|
||||
|
||||
const references = createMarkdownReferences(workspace, noteA.uri, false);
|
||||
expect(references.map(r => r.url)).toEqual(['page-b', 'image.png']);
|
||||
});
|
||||
|
||||
it('should use relative paths', () => {
|
||||
const workspace = createTestWorkspace();
|
||||
const noteA = createNoteFromMarkdown(
|
||||
|
||||
@@ -8,64 +8,19 @@ import { isNone, isSome } from '../utils';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../model/uri';
|
||||
import { FoamWorkspace } from '../model/workspace';
|
||||
import { IDataStore, IMatcher, IWatcher } from '../services/datastore';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
import { ResourceProvider } from '../model/provider';
|
||||
import { MarkdownLink } from './markdown-link';
|
||||
import { IDataStore } from './datastore';
|
||||
|
||||
export class MarkdownResourceProvider implements ResourceProvider {
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly matcher: IMatcher,
|
||||
private readonly dataStore: IDataStore,
|
||||
private readonly parser: ResourceParser,
|
||||
private readonly watcher?: IWatcher
|
||||
private readonly parser: ResourceParser
|
||||
) {}
|
||||
|
||||
async init(workspace: FoamWorkspace) {
|
||||
const filesByFolder = await Promise.all(
|
||||
this.matcher.include.map(glob =>
|
||||
this.dataStore.list(glob, this.matcher.exclude)
|
||||
)
|
||||
);
|
||||
const files = this.matcher
|
||||
.match(filesByFolder.flat())
|
||||
.filter(this.supports);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async uri => {
|
||||
Logger.debug('Found: ' + uri.toString());
|
||||
const content = await this.dataStore.read(uri);
|
||||
if (isSome(content)) {
|
||||
workspace.set(this.parser.parse(uri, content));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (this.watcher != null) {
|
||||
this.disposables = [
|
||||
this.watcher.onDidChange(async uri => {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
const content = await this.dataStore.read(uri);
|
||||
isSome(content) &&
|
||||
workspace.set(await this.parser.parse(uri, content));
|
||||
}
|
||||
}),
|
||||
this.watcher.onDidCreate(async uri => {
|
||||
if (this.matcher.isMatch(uri) && this.supports(uri)) {
|
||||
const content = await this.dataStore.read(uri);
|
||||
isSome(content) &&
|
||||
workspace.set(await this.parser.parse(uri, content));
|
||||
}
|
||||
}),
|
||||
this.watcher.onDidDelete(uri => {
|
||||
this.supports(uri) && workspace.delete(uri);
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
supports(uri: URI) {
|
||||
return uri.isMarkdown();
|
||||
}
|
||||
@@ -181,7 +136,7 @@ to generate markdown reference list`
|
||||
}
|
||||
|
||||
let relativeUri = target.uri.relativeTo(noteUri.getDirectory());
|
||||
if (!includeExtension) {
|
||||
if (!includeExtension && relativeUri.path.endsWith('.md')) {
|
||||
relativeUri = relativeUri.changeExtension('*', '');
|
||||
}
|
||||
|
||||
|
||||
30
packages/foam-vscode/src/core/utils/path.test.ts
Normal file
30
packages/foam-vscode/src/core/utils/path.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { asAbsolutePaths } from './path';
|
||||
|
||||
describe('path utils', () => {
|
||||
describe('asAbsolutePaths', () => {
|
||||
it('returns the path if already absolute', () => {
|
||||
const paths = asAbsolutePaths('/path/to/test', [
|
||||
'/root/Users',
|
||||
'/root/tmp',
|
||||
]);
|
||||
expect(paths).toEqual(['/path/to/test']);
|
||||
});
|
||||
it('returns the matching base if found', () => {
|
||||
const paths = asAbsolutePaths('tmp/to/test', [
|
||||
'/root/Users',
|
||||
'/root/tmp',
|
||||
]);
|
||||
expect(paths).toEqual(['/root/tmp/to/test']);
|
||||
});
|
||||
it('returns all bases if no match is found', () => {
|
||||
const paths = asAbsolutePaths('path/to/test', [
|
||||
'/root/Users',
|
||||
'/root/tmp',
|
||||
]);
|
||||
expect(paths).toEqual([
|
||||
'/root/Users/path/to/test',
|
||||
'/root/tmp/path/to/test',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CharCode } from '../common/charCode';
|
||||
import { posix } from 'path';
|
||||
import { isNone } from './core';
|
||||
|
||||
/**
|
||||
* Converts filesystem path to POSIX path. Supported inputs are:
|
||||
@@ -174,3 +175,46 @@ function parseUNCShare(uncPath: string): [string, string] {
|
||||
return [uncPath.substring(2, idx), uncPath.substring(idx) || '\\'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a relative path into an absolute path given a collection of base folders.
|
||||
* - if no base folder is provided, it will throw
|
||||
* - if the given path is already absolute, it will return it
|
||||
* - if the given path is relative it will return absolute paths for the ones matching the
|
||||
* first part of the path
|
||||
* - if no matching base folder is found, it will return an absolute path per base folder
|
||||
* @param path the path to evaluate
|
||||
* @param baseFolders the base folders to use
|
||||
* @returns an array of absolute path, guaranteed to have at least 1 element
|
||||
*/
|
||||
export function asAbsolutePaths(path: string, baseFolders: string[]): string[] {
|
||||
if (isNone(baseFolders) || baseFolders.length === 0) {
|
||||
throw new Error('Cannot compute absolute URI without a base');
|
||||
}
|
||||
|
||||
if (isAbsolute(path)) {
|
||||
return [path];
|
||||
}
|
||||
let tokens = path.split('/');
|
||||
const firstDir = tokens[0];
|
||||
const res = [];
|
||||
if (baseFolders.length > 1) {
|
||||
for (const folder of baseFolders) {
|
||||
const lastDir = folder.split('/').pop();
|
||||
if (lastDir === firstDir) {
|
||||
tokens = tokens.slice(1);
|
||||
res.push([folder, ...tokens].join('/'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (res.length === 0) {
|
||||
for (const folder of baseFolders) {
|
||||
const match = folder.endsWith('/')
|
||||
? folder.substring(0, folder.length - 1)
|
||||
: folder;
|
||||
res.push([match, ...tokens].join('/'));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { workspace } from 'vscode';
|
||||
import dateFormat from 'dateformat';
|
||||
import { focusNote } from './utils';
|
||||
import { URI } from './core/model/uri';
|
||||
import { fromVsCodeUri, toVsCodeUri } from './utils/vsc-utils';
|
||||
import { toVsCodeUri } from './utils/vsc-utils';
|
||||
import { NoteFactory } from './services/templates';
|
||||
import { getFoamVsCodeConfig } from './services/config';
|
||||
import { asAbsoluteWorkspaceUri } from './services/editor';
|
||||
|
||||
/**
|
||||
* Open the daily note file.
|
||||
@@ -42,17 +43,9 @@ export async function openDailyNoteFor(date?: Date) {
|
||||
*/
|
||||
export function getDailyNotePath(date: Date): URI {
|
||||
const folder = getFoamVsCodeConfig<string>('openDailyNote.directory') ?? '.';
|
||||
const dailyNoteDirectory = URI.file(folder);
|
||||
const dailyNoteDirectory = asAbsoluteWorkspaceUri(URI.file(folder));
|
||||
const dailyNoteFilename = getDailyNoteFileName(date);
|
||||
|
||||
if (dailyNoteDirectory.isAbsolute()) {
|
||||
return dailyNoteDirectory.joinPath(dailyNoteFilename);
|
||||
} else {
|
||||
return fromVsCodeUri(workspace.workspaceFolders[0].uri).joinPath(
|
||||
dailyNoteDirectory.path,
|
||||
dailyNoteFilename
|
||||
);
|
||||
}
|
||||
return dailyNoteDirectory.joinPath(dailyNoteFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { workspace, ExtensionContext, window, commands } from 'vscode';
|
||||
import { MarkdownResourceProvider } from './core/services/markdown-provider';
|
||||
import { bootstrap } from './core/model/foam';
|
||||
import { URI } from './core/model/uri';
|
||||
import { FileDataStore, Matcher } from './core/services/datastore';
|
||||
import { Logger } from './core/utils/log';
|
||||
|
||||
import { features } from './features';
|
||||
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
|
||||
import { getIgnoredFilesSetting } from './settings';
|
||||
import { fromVsCodeUri, toVsCodeUri } from './utils/vsc-utils';
|
||||
import { AttachmentResourceProvider } from './core/services/attachment-provider';
|
||||
import { VsCodeWatcher } from './services/watcher';
|
||||
import { createMarkdownParser } from './core/services/markdown-parser';
|
||||
import VsCodeBasedParserCache from './services/cache';
|
||||
import { createMatcherAndDataStore } from './services/editor';
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const logger = new VsCodeOutputLogger();
|
||||
@@ -22,34 +20,36 @@ export async function activate(context: ExtensionContext) {
|
||||
try {
|
||||
Logger.info('Starting Foam');
|
||||
|
||||
if (workspace.workspaceFolders === undefined) {
|
||||
Logger.info('No workspace open. Foam will not start');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare Foam
|
||||
const readFile = async (uri: URI) =>
|
||||
(await workspace.fs.readFile(toVsCodeUri(uri))).toString();
|
||||
const dataStore = new FileDataStore(readFile);
|
||||
const matcher = new Matcher(
|
||||
workspace.workspaceFolders.map(dir => fromVsCodeUri(dir.uri)),
|
||||
['**/*'],
|
||||
getIgnoredFilesSetting().map(g => g.toString())
|
||||
);
|
||||
const excludes = getIgnoredFilesSetting().map(g => g.toString());
|
||||
const {
|
||||
matcher,
|
||||
dataStore,
|
||||
excludePatterns,
|
||||
} = await createMatcherAndDataStore(excludes);
|
||||
|
||||
Logger.info('Loading from directories:');
|
||||
for (const folder of workspace.workspaceFolders) {
|
||||
Logger.info('- ' + folder.uri.fsPath);
|
||||
Logger.info(' Include: **/*');
|
||||
Logger.info(' Exclude: ' + excludePatterns.get(folder.name).join(','));
|
||||
}
|
||||
|
||||
const watcher = new VsCodeWatcher(
|
||||
workspace.createFileSystemWatcher('**/*')
|
||||
);
|
||||
const parserCache = new VsCodeBasedParserCache(context);
|
||||
const parser = createMarkdownParser([], parserCache);
|
||||
|
||||
const markdownProvider = new MarkdownResourceProvider(
|
||||
matcher,
|
||||
dataStore,
|
||||
parser,
|
||||
watcher
|
||||
);
|
||||
const attachmentProvider = new AttachmentResourceProvider(
|
||||
matcher,
|
||||
dataStore,
|
||||
watcher
|
||||
);
|
||||
const markdownProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
const attachmentProvider = new AttachmentResourceProvider();
|
||||
|
||||
const foamPromise = bootstrap(matcher, dataStore, parser, [
|
||||
const foamPromise = bootstrap(matcher, watcher, dataStore, parser, [
|
||||
markdownProvider,
|
||||
attachmentProvider,
|
||||
]);
|
||||
@@ -59,6 +59,7 @@ export async function activate(context: ExtensionContext) {
|
||||
|
||||
const foam = await foamPromise;
|
||||
Logger.info(`Loaded ${foam.workspace.list().length} resources`);
|
||||
|
||||
context.subscriptions.push(
|
||||
foam,
|
||||
watcher,
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import { commands, ExtensionContext } from 'vscode';
|
||||
import { commands, window, ExtensionContext } from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { DEFAULT_TEMPLATE_URI, NoteFactory } from '../../services/templates';
|
||||
import { getDefaultTemplateUri, NoteFactory } from '../../services/templates';
|
||||
import { Resolver } from '../../services/variable-resolver';
|
||||
|
||||
/**
|
||||
* Create a new note from the default template.
|
||||
*
|
||||
* @deprecated use 'foam-vscode.create-note' instead
|
||||
*/
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: ExtensionContext) => {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
'foam-vscode.create-note-from-default-template',
|
||||
() => {
|
||||
window.showWarningMessage(
|
||||
"This command is deprecated, use 'Foam: Create Note' (foam-vscode.create-note) instead"
|
||||
);
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
|
||||
return NoteFactory.createFromTemplate(
|
||||
DEFAULT_TEMPLATE_URI,
|
||||
getDefaultTemplateUri(),
|
||||
resolver,
|
||||
undefined,
|
||||
`---
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { commands, ExtensionContext, QuickPickItem, window } from 'vscode';
|
||||
import { commands, ExtensionContext } from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import {
|
||||
getTemplateMetadata,
|
||||
getTemplates,
|
||||
NoteFactory,
|
||||
TEMPLATES_DIR,
|
||||
} from '../../services/templates';
|
||||
import { askUserForTemplate, NoteFactory } from '../../services/templates';
|
||||
import { Resolver } from '../../services/variable-resolver';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
@@ -14,102 +9,17 @@ const feature: FoamFeature = {
|
||||
commands.registerCommand(
|
||||
'foam-vscode.create-note-from-template',
|
||||
async () => {
|
||||
const selectedTemplate = await askUserForTemplate();
|
||||
if (selectedTemplate === undefined) {
|
||||
return;
|
||||
const templateUri = await askUserForTemplate();
|
||||
|
||||
if (templateUri) {
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
|
||||
await NoteFactory.createFromTemplate(templateUri, resolver);
|
||||
}
|
||||
const templateFilename =
|
||||
(selectedTemplate as QuickPickItem).description ||
|
||||
(selectedTemplate as QuickPickItem).label;
|
||||
const templateUri = TEMPLATES_DIR.joinPath(templateFilename);
|
||||
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
|
||||
await NoteFactory.createFromTemplate(templateUri, resolver);
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
async function offerToCreateTemplate(): Promise<void> {
|
||||
const response = await window.showQuickPick(['Yes', 'No'], {
|
||||
placeHolder:
|
||||
'No templates available. Would you like to create one instead?',
|
||||
});
|
||||
if (response === 'Yes') {
|
||||
commands.executeCommand('foam-vscode.create-new-template');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function sortTemplatesMetadata(
|
||||
t1: Map<string, string>,
|
||||
t2: Map<string, string>
|
||||
) {
|
||||
// Sort by name's existence, then name, then path
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (t1.get('name') !== undefined && t2.get('name') === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const pathSortOrder = t1
|
||||
.get('templatePath')
|
||||
.localeCompare(t2.get('templatePath'));
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') === undefined) {
|
||||
return pathSortOrder;
|
||||
}
|
||||
|
||||
const nameSortOrder = t1.get('name').localeCompare(t2.get('name'));
|
||||
|
||||
return nameSortOrder || pathSortOrder;
|
||||
}
|
||||
|
||||
async function askUserForTemplate() {
|
||||
const templates = await getTemplates();
|
||||
if (templates.length === 0) {
|
||||
return offerToCreateTemplate();
|
||||
}
|
||||
|
||||
const templatesMetadata = (
|
||||
await Promise.all(
|
||||
templates.map(async templateUri => {
|
||||
const metadata = await getTemplateMetadata(templateUri);
|
||||
metadata.set('templatePath', templateUri.getBasename());
|
||||
return metadata;
|
||||
})
|
||||
)
|
||||
).sort(sortTemplatesMetadata);
|
||||
|
||||
const items: QuickPickItem[] = await Promise.all(
|
||||
templatesMetadata.map(metadata => {
|
||||
const label = metadata.get('name') || metadata.get('templatePath');
|
||||
const description = metadata.get('name')
|
||||
? metadata.get('templatePath')
|
||||
: null;
|
||||
const detail = metadata.get('description');
|
||||
const item = {
|
||||
label: label,
|
||||
description: description,
|
||||
detail: detail,
|
||||
};
|
||||
Object.keys(item).forEach(key => {
|
||||
if (!item[key]) {
|
||||
delete item[key];
|
||||
}
|
||||
});
|
||||
return item;
|
||||
})
|
||||
);
|
||||
|
||||
return await window.showQuickPick(items, {
|
||||
placeHolder: 'Select a template to use.',
|
||||
});
|
||||
}
|
||||
|
||||
export default feature;
|
||||
|
||||
129
packages/foam-vscode/src/features/commands/create-note.spec.ts
Normal file
129
packages/foam-vscode/src/features/commands/create-note.spec.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { commands, window } from 'vscode';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import { asAbsoluteWorkspaceUri, readFile } from '../../services/editor';
|
||||
import {
|
||||
closeEditors,
|
||||
createFile,
|
||||
deleteFile,
|
||||
expectSameUri,
|
||||
getUriInWorkspace,
|
||||
} from '../../test/test-utils-vscode';
|
||||
|
||||
describe('create-note command', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses sensible defaults to work even without params', async () => {
|
||||
const spy = jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve('Test note')));
|
||||
|
||||
await commands.executeCommand('foam-vscode.create-note');
|
||||
expect(spy).toBeCalled();
|
||||
const target = asAbsoluteWorkspaceUri(URI.file('Test note.md'));
|
||||
expectSameUri(target, window.activeTextEditor?.document.uri);
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('gives precedence to the template over the text', async () => {
|
||||
const templateA = await createFile('Template A', [
|
||||
'.foam',
|
||||
'templates',
|
||||
'template-for-create-note.md',
|
||||
]);
|
||||
const target = getUriInWorkspace();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.path,
|
||||
templatePath: templateA.uri.path,
|
||||
text: 'hello',
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual('Template A');
|
||||
expectSameUri(window.activeTextEditor.document.uri, target);
|
||||
await deleteFile(target);
|
||||
await deleteFile(templateA.uri);
|
||||
});
|
||||
|
||||
it('focuses on the newly created note', async () => {
|
||||
const target = getUriInWorkspace();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.path,
|
||||
text: 'hello',
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual('hello');
|
||||
expectSameUri(window.activeTextEditor.document.uri, target);
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('supports variables', async () => {
|
||||
const target = getUriInWorkspace();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.path,
|
||||
text: 'hello ${FOAM_TITLE}', // eslint-disable-line no-template-curly-in-string
|
||||
variables: { FOAM_TITLE: 'world' },
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual('hello world');
|
||||
expectSameUri(window.activeTextEditor.document.uri, target);
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('supports date variables', async () => {
|
||||
const target = getUriInWorkspace();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.path,
|
||||
text: 'hello ${FOAM_DATE_YEAR}', // eslint-disable-line no-template-curly-in-string
|
||||
date: '2021-10-01',
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual('hello 2021');
|
||||
expectSameUri(window.activeTextEditor.document.uri, target);
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('supports various options to deal with existing notes', async () => {
|
||||
const target = await createFile('hello');
|
||||
const content = await readFile(target.uri);
|
||||
expect(content).toEqual('hello');
|
||||
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.uri.path,
|
||||
text: 'test overwrite',
|
||||
onFileExists: 'overwrite',
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'test overwrite'
|
||||
);
|
||||
expectSameUri(window.activeTextEditor.document.uri, target.uri);
|
||||
|
||||
await closeEditors();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.uri.path,
|
||||
text: 'test open',
|
||||
onFileExists: 'open',
|
||||
});
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'test overwrite'
|
||||
);
|
||||
expectSameUri(window.activeTextEditor.document.uri, target.uri);
|
||||
|
||||
await closeEditors();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.uri.path,
|
||||
text: 'test cancel',
|
||||
onFileExists: 'cancel',
|
||||
});
|
||||
expect(window.activeTextEditor).toBeUndefined();
|
||||
|
||||
const spy = jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(undefined)));
|
||||
await closeEditors();
|
||||
await commands.executeCommand('foam-vscode.create-note', {
|
||||
notePath: target.uri.path,
|
||||
text: 'test ask',
|
||||
onFileExists: 'ask',
|
||||
});
|
||||
expect(spy).toBeCalled();
|
||||
|
||||
await deleteFile(target);
|
||||
});
|
||||
});
|
||||
112
packages/foam-vscode/src/features/commands/create-note.ts
Normal file
112
packages/foam-vscode/src/features/commands/create-note.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import {
|
||||
askUserForTemplate,
|
||||
getDefaultTemplateUri,
|
||||
getPathFromTitle,
|
||||
NoteFactory,
|
||||
} from '../../services/templates';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { Resolver } from '../../services/variable-resolver';
|
||||
import { asAbsoluteWorkspaceUri, fileExists } from '../../services/editor';
|
||||
import { isSome } from '../../core/utils';
|
||||
|
||||
interface CreateNoteArgs {
|
||||
/**
|
||||
* The path of the note to create.
|
||||
* If relative it will be resolved against the workspace root.
|
||||
*/
|
||||
notePath?: string;
|
||||
/**
|
||||
* The path of the template to use.
|
||||
*/
|
||||
templatePath?: string;
|
||||
/**
|
||||
* Whether to ask the user to select a template for the new note. If so, overwrites templatePath.
|
||||
*/
|
||||
askForTemplate?: boolean;
|
||||
/**
|
||||
* The text to use for the note.
|
||||
* If a template is provided, the template has precedence
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* Variables to use in the text or template
|
||||
*/
|
||||
variables?: Map<string, string>;
|
||||
/**
|
||||
* The date used to resolve the FOAM_DATE_* variables. in YYYY-MM-DD format
|
||||
*/
|
||||
date?: string;
|
||||
/**
|
||||
* What to do in case the target file already exists
|
||||
*/
|
||||
onFileExists?: 'overwrite' | 'open' | 'ask' | 'cancel';
|
||||
}
|
||||
|
||||
const DEFAULT_NEW_NOTE_TEXT = `# \${FOAM_TITLE}
|
||||
|
||||
\${FOAM_SELECTED_TEXT}`;
|
||||
|
||||
async function createNote(args: CreateNoteArgs) {
|
||||
args = args ?? {};
|
||||
const date = isSome(args.date) ? new Date(Date.parse(args.date)) : new Date();
|
||||
const resolver = new Resolver(
|
||||
new Map(Object.entries(args.variables ?? {})),
|
||||
date
|
||||
);
|
||||
const text = args.text ?? DEFAULT_NEW_NOTE_TEXT;
|
||||
const noteUri =
|
||||
args.notePath && asAbsoluteWorkspaceUri(URI.file(args.notePath));
|
||||
let templateUri: URI;
|
||||
if (args.askForTemplate) {
|
||||
const selectedTemplate = await askUserForTemplate();
|
||||
if (selectedTemplate) {
|
||||
templateUri = selectedTemplate;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
templateUri = args.templatePath
|
||||
? asAbsoluteWorkspaceUri(URI.file(args.templatePath))
|
||||
: getDefaultTemplateUri();
|
||||
}
|
||||
|
||||
if (await fileExists(templateUri)) {
|
||||
return NoteFactory.createFromTemplate(
|
||||
templateUri,
|
||||
resolver,
|
||||
noteUri,
|
||||
text,
|
||||
args.onFileExists
|
||||
);
|
||||
} else {
|
||||
return NoteFactory.createNote(
|
||||
noteUri ?? (await getPathFromTitle(resolver)),
|
||||
text,
|
||||
resolver,
|
||||
args.onFileExists
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CREATE_NOTE_COMMAND = {
|
||||
command: 'foam-vscode.create-note',
|
||||
title: 'Foam: Create Note',
|
||||
|
||||
asURI: (args: CreateNoteArgs) =>
|
||||
vscode.Uri.parse(`command:${CREATE_NOTE_COMMAND.command}`).with({
|
||||
query: encodeURIComponent(JSON.stringify(args)),
|
||||
}),
|
||||
};
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: vscode.ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(CREATE_NOTE_COMMAND.command, createNote)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
@@ -10,3 +10,4 @@ export { default as openRandomNoteCommand } from './open-random-note';
|
||||
export { default as openResource } from './open-resource';
|
||||
export { default as updateGraphCommand } from './update-graph';
|
||||
export { default as updateWikilinksCommand } from './update-wikilinks';
|
||||
export { default as createNote } from './create-note';
|
||||
|
||||
@@ -28,13 +28,12 @@ const feature: FoamFeature = {
|
||||
uri.path === vscode.window.activeTextEditor?.document.uri.path
|
||||
? vscode.window.activeTextEditor?.document.uri
|
||||
: toVsCodeUri(uri.asPlain());
|
||||
// if the doc is already open, reuse the same colunm
|
||||
const targetEditor = vscode.window.visibleTextEditors.find(
|
||||
ed => targetUri.path === ed.document.uri.path
|
||||
);
|
||||
const column = targetEditor?.viewColumn;
|
||||
return vscode.window.showTextDocument(targetUri, {
|
||||
viewColumn: column,
|
||||
});
|
||||
return vscode.commands.executeCommand('vscode.open', targetUri);
|
||||
}
|
||||
case 'placeholder': {
|
||||
const title = uri.getName();
|
||||
|
||||
@@ -206,8 +206,7 @@ const feature: FoamFeature = {
|
||||
languages.registerCompletionItemProvider(
|
||||
'markdown',
|
||||
datesCompletionProvider,
|
||||
'/',
|
||||
'+'
|
||||
'/'
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -3,30 +3,26 @@ import { createMarkdownParser } from '../core/services/markdown-parser';
|
||||
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { FileDataStore, Matcher } from '../core/services/datastore';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createFile,
|
||||
showInEditor,
|
||||
} from '../test/test-utils-vscode';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { HoverProvider } from './hover-provider';
|
||||
import { readFileFromFs } from '../test/test-utils';
|
||||
import { FileDataStore } from '../test/test-datastore';
|
||||
|
||||
// We can't use createTestWorkspace from /packages/foam-vscode/src/test/test-utils.ts
|
||||
// because we need a MarkdownResourceProvider with a real instance of FileDataStore.
|
||||
const createWorkspace = () => {
|
||||
const matcher = new Matcher(
|
||||
vscode.workspace.workspaceFolders.map(f => fromVsCodeUri(f.uri))
|
||||
const dataStore = new FileDataStore(
|
||||
readFileFromFs,
|
||||
vscode.workspace.workspaceFolders[0].uri.fsPath
|
||||
);
|
||||
const dataStore = new FileDataStore(readFileFromFs);
|
||||
const parser = createMarkdownParser();
|
||||
const resourceProvider = new MarkdownResourceProvider(
|
||||
matcher,
|
||||
dataStore,
|
||||
parser
|
||||
);
|
||||
const resourceProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
const workspace = new FoamWorkspace();
|
||||
workspace.registerProvider(resourceProvider);
|
||||
return workspace;
|
||||
@@ -179,7 +175,7 @@ describe('Hover provider', () => {
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(getValue(result.contents[0])).toEqual(
|
||||
`This is some content from file B`
|
||||
);
|
||||
@@ -205,7 +201,7 @@ describe('Hover provider', () => {
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(getValue(result.contents[0])).toEqual(
|
||||
`This is some content from file B`
|
||||
);
|
||||
@@ -235,7 +231,7 @@ The content of file B`);
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(getValue(result.contents[0])).toEqual(`The content of file B`);
|
||||
ws.dispose();
|
||||
graph.dispose();
|
||||
@@ -255,9 +251,12 @@ The content of file B`);
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(result.contents[0]).toEqual(null);
|
||||
expect(result.contents[1]).toEqual(null);
|
||||
expect(getValue(result.contents[2])).toMatch(
|
||||
"[Create note from template for 'wikilink'](command:foam-vscode.create-note?"
|
||||
);
|
||||
ws.dispose();
|
||||
graph.dispose();
|
||||
});
|
||||
@@ -281,11 +280,12 @@ The content of file B`);
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(getValue(result.contents[0])).toEqual(`This is some content`);
|
||||
expect(getValue(result.contents[1])).toMatch(
|
||||
/^Also referenced in 1 note:/
|
||||
);
|
||||
expect(result.contents[2]).toEqual(null);
|
||||
ws.dispose();
|
||||
graph.dispose();
|
||||
});
|
||||
@@ -309,7 +309,7 @@ The content of file B`);
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(getValue(result.contents[1])).toMatch(
|
||||
/^Also referenced in 1 note:/
|
||||
);
|
||||
@@ -333,12 +333,14 @@ The content of file B`);
|
||||
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
|
||||
const result = await provider.provideHover(doc, pos, noCancelToken);
|
||||
|
||||
expect(result.contents).toHaveLength(2);
|
||||
expect(result.contents).toHaveLength(3);
|
||||
expect(result.contents[0]).toEqual(null);
|
||||
expect(getValue(result.contents[1])).toMatch(
|
||||
/^Also referenced in 2 notes:/
|
||||
);
|
||||
|
||||
expect(getValue(result.contents[2])).toMatch(
|
||||
"[Create note from template for 'placeholder'](command:foam-vscode.create-note?"
|
||||
);
|
||||
ws.dispose();
|
||||
graph.dispose();
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { Range } from '../core/model/range';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { OPEN_COMMAND } from './commands/open-resource';
|
||||
import { CREATE_NOTE_COMMAND } from './commands/create-note';
|
||||
|
||||
export const CONFIG_KEY = 'links.hover.enable';
|
||||
|
||||
@@ -108,8 +109,36 @@ export class HoverProvider implements vscode.HoverProvider {
|
||||
: this.workspace.get(targetUri).title;
|
||||
}
|
||||
|
||||
// If placeholder, offer to create a new note from template (compared to default link provider - not from template)
|
||||
const basedir =
|
||||
vscode.workspace.workspaceFolders.length > 0
|
||||
? vscode.workspace.workspaceFolders[0].uri
|
||||
: vscode.window.activeTextEditor?.document.uri
|
||||
? vscode.window.activeTextEditor!.document.uri
|
||||
: undefined;
|
||||
if (basedir === undefined) {
|
||||
return;
|
||||
}
|
||||
const target = fromVsCodeUri(basedir)
|
||||
.resolve(targetUri, true)
|
||||
.changeExtension('', '.md');
|
||||
const args = {
|
||||
text: target.getName(),
|
||||
notePath: target.path,
|
||||
askForTemplate: true,
|
||||
};
|
||||
const command = CREATE_NOTE_COMMAND.asURI(args);
|
||||
const newNoteFromTemplate = new vscode.MarkdownString(
|
||||
`[Create note from template for '${targetUri.getName()}'](${command})`
|
||||
);
|
||||
newNoteFromTemplate.isTrusted = true;
|
||||
|
||||
const hover: vscode.Hover = {
|
||||
contents: [mdContent, sources.length > 0 ? references : null],
|
||||
contents: [
|
||||
mdContent,
|
||||
sources.length > 0 ? references : null,
|
||||
targetUri.isPlaceholder() ? newNoteFromTemplate : null,
|
||||
],
|
||||
range: toVsCodeRange(targetLink.range),
|
||||
};
|
||||
return hover;
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
import { FoamFeature } from '../types';
|
||||
import * as commands from './commands';
|
||||
import dataviz from './dataviz';
|
||||
import * as panels from './panels';
|
||||
import dateSnippets from './date-snippets';
|
||||
import tagsExplorer from './tags-tree-view';
|
||||
import orphans from './orphans';
|
||||
import placeholders from './placeholders';
|
||||
import backlinks from './backlinks';
|
||||
import hoverProvider from './hover-provider';
|
||||
import previewNavigation from './preview';
|
||||
import preview from './preview';
|
||||
import completionProvider, { completionCursorMove } from './link-completion';
|
||||
import tagCompletionProvider from './tag-completion';
|
||||
import linkDecorations from './document-decorator';
|
||||
import navigationProviders from './navigation-provider';
|
||||
import wikilinkDiagnostics from './wikilink-diagnostics';
|
||||
import refactor from './refactor';
|
||||
import { FoamFeature } from '../types';
|
||||
|
||||
export const features: FoamFeature[] = [
|
||||
...Object.values(commands),
|
||||
...Object.values(panels),
|
||||
refactor,
|
||||
navigationProviders,
|
||||
wikilinkDiagnostics,
|
||||
tagsExplorer,
|
||||
dataviz,
|
||||
dateSnippets,
|
||||
orphans,
|
||||
placeholders,
|
||||
backlinks,
|
||||
hoverProvider,
|
||||
linkDecorations,
|
||||
previewNavigation,
|
||||
preview,
|
||||
completionProvider,
|
||||
tagCompletionProvider,
|
||||
completionCursorMove,
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
closeEditors,
|
||||
createFile,
|
||||
showInEditor,
|
||||
withModifiedFoamConfiguration,
|
||||
} from '../test/test-utils-vscode';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
import {
|
||||
CompletionProvider,
|
||||
WikilinkCompletionProvider,
|
||||
SectionCompletionProvider,
|
||||
} from './link-completion';
|
||||
|
||||
@@ -63,7 +64,7 @@ describe('Link Completion', () => {
|
||||
it('should not return any link for empty documents', async () => {
|
||||
const { uri } = await createFile('');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new CompletionProvider(ws, graph);
|
||||
const provider = new WikilinkCompletionProvider(ws, graph);
|
||||
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
@@ -76,7 +77,7 @@ describe('Link Completion', () => {
|
||||
it('should not return link outside the wikilink brackets', async () => {
|
||||
const { uri } = await createFile('[[file]] then');
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new CompletionProvider(ws, graph);
|
||||
const provider = new WikilinkCompletionProvider(ws, graph);
|
||||
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
@@ -90,7 +91,7 @@ describe('Link Completion', () => {
|
||||
for (const text of ['[[', '[[file]] [[', '[[file]] #tag [[']) {
|
||||
const { uri } = await createFile(text);
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new CompletionProvider(ws, graph);
|
||||
const provider = new WikilinkCompletionProvider(ws, graph);
|
||||
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
@@ -110,6 +111,103 @@ describe('Link Completion', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should support label setting', async () => {
|
||||
const { uri: noteUri, content } = await createFile(`# My Note Title`);
|
||||
const workspace = createTestWorkspace();
|
||||
workspace.set(parser.parse(noteUri, content));
|
||||
const provider = new WikilinkCompletionProvider(
|
||||
workspace,
|
||||
FoamGraph.fromWorkspace(workspace)
|
||||
);
|
||||
|
||||
const { uri } = await createFile('[[');
|
||||
const { doc } = await showInEditor(uri);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
'completion.label',
|
||||
'title',
|
||||
async () => {
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 3)
|
||||
);
|
||||
|
||||
expect(links.items.map(i => i.label)).toEqual(['My Note Title']);
|
||||
}
|
||||
);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
'completion.label',
|
||||
'path',
|
||||
async () => {
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 3)
|
||||
);
|
||||
|
||||
expect(links.items.map(i => i.label)).toEqual([noteUri.getBasename()]);
|
||||
}
|
||||
);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
'completion.label',
|
||||
'identifier',
|
||||
async () => {
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 3)
|
||||
);
|
||||
|
||||
expect(links.items.map(i => i.label)).toEqual([
|
||||
workspace.getIdentifier(noteUri),
|
||||
]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should support alias setting', async () => {
|
||||
const { uri: noteUri, content } = await createFile(`# My Note Title`);
|
||||
const workspace = createTestWorkspace();
|
||||
workspace.set(parser.parse(noteUri, content));
|
||||
const provider = new WikilinkCompletionProvider(
|
||||
workspace,
|
||||
FoamGraph.fromWorkspace(workspace)
|
||||
);
|
||||
|
||||
const { uri } = await createFile('[[');
|
||||
const { doc } = await showInEditor(uri);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
'completion.useAlias',
|
||||
'never',
|
||||
async () => {
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 3)
|
||||
);
|
||||
|
||||
expect(links.items.map(i => i.insertText)).toEqual([
|
||||
workspace.getIdentifier(noteUri),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
await withModifiedFoamConfiguration(
|
||||
'completion.useAlias',
|
||||
'whenPathDiffersFromTitle',
|
||||
async () => {
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
new vscode.Position(0, 3)
|
||||
);
|
||||
|
||||
expect(links.items.map(i => i.insertText)).toEqual([
|
||||
`${workspace.getIdentifier(noteUri)}|My Note Title`,
|
||||
]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return sections for other notes', async () => {
|
||||
for (const text of [
|
||||
'[[file-name#',
|
||||
@@ -171,7 +269,7 @@ alias: alias-a
|
||||
ws.set(parser.parse(uri, content));
|
||||
|
||||
const { doc } = await showInEditor(uri);
|
||||
const provider = new CompletionProvider(ws, graph);
|
||||
const provider = new WikilinkCompletionProvider(ws, graph);
|
||||
|
||||
const links = await provider.provideCompletionItems(
|
||||
doc,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { Resource } from '../core/model/note';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { getFoamVsCodeConfig } from '../services/config';
|
||||
import { FoamFeature } from '../types';
|
||||
import { getNoteTooltip, mdDocSelector } from '../utils';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
@@ -28,7 +30,7 @@ const feature: FoamFeature = {
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
mdDocSelector,
|
||||
new CompletionProvider(foam.workspace, foam.graph),
|
||||
new WikilinkCompletionProvider(foam.workspace, foam.graph),
|
||||
'['
|
||||
),
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
@@ -159,7 +161,7 @@ export class SectionCompletionProvider
|
||||
}
|
||||
}
|
||||
|
||||
export class CompletionProvider
|
||||
export class WikilinkCompletionProvider
|
||||
implements vscode.CompletionItemProvider<vscode.CompletionItem> {
|
||||
constructor(private ws: FoamWorkspace, private graph: FoamGraph) {}
|
||||
|
||||
@@ -186,20 +188,45 @@ export class CompletionProvider
|
||||
position.line,
|
||||
position.character
|
||||
);
|
||||
const labelStyle = getCompletionLabelSetting();
|
||||
const aliasSetting = getCompletionAliasSetting();
|
||||
|
||||
const resources = this.ws.list().map(resource => {
|
||||
const label = vscode.workspace.asRelativePath(toVsCodeUri(resource.uri));
|
||||
const resourceIsDocument =
|
||||
['attachment', 'image'].indexOf(resource.type) === -1;
|
||||
|
||||
const identifier = this.ws.getIdentifier(resource.uri);
|
||||
|
||||
const label = !resourceIsDocument
|
||||
? identifier
|
||||
: labelStyle === 'path'
|
||||
? vscode.workspace.asRelativePath(toVsCodeUri(resource.uri))
|
||||
: labelStyle === 'title'
|
||||
? resource.title
|
||||
: identifier;
|
||||
|
||||
const item = new ResourceCompletionItem(
|
||||
label,
|
||||
vscode.CompletionItemKind.File,
|
||||
resource.uri
|
||||
);
|
||||
item.sortText =
|
||||
resource.type === 'attachment' ? `1-${item.label}` : `0-${item.label}`;
|
||||
item.filterText = resource.uri.getName();
|
||||
item.insertText = this.ws.getIdentifier(resource.uri);
|
||||
|
||||
item.detail = vscode.workspace.asRelativePath(toVsCodeUri(resource.uri));
|
||||
item.sortText = resourceIsDocument
|
||||
? `0-${item.label}`
|
||||
: `1-${item.label}`;
|
||||
|
||||
const useAlias =
|
||||
resourceIsDocument &&
|
||||
aliasSetting !== 'never' &&
|
||||
wikilinkRequiresAlias(resource);
|
||||
|
||||
item.insertText = useAlias
|
||||
? `${identifier}|${resource.title}`
|
||||
: identifier;
|
||||
item.commitCharacters = useAlias ? [] : linkCommitCharacters;
|
||||
item.range = replacementRange;
|
||||
item.command = COMPLETION_CURSOR_MOVE;
|
||||
item.commitCharacters = linkCommitCharacters;
|
||||
return item;
|
||||
});
|
||||
const aliases = this.ws.list().flatMap(resource =>
|
||||
@@ -265,4 +292,23 @@ class ResourceCompletionItem extends vscode.CompletionItem {
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletionLabelSetting() {
|
||||
const labelStyle: 'path' | 'title' | 'identifier' = getFoamVsCodeConfig(
|
||||
'completion.label'
|
||||
);
|
||||
return labelStyle;
|
||||
}
|
||||
|
||||
function getCompletionAliasSetting() {
|
||||
const aliasStyle: 'never' | 'whenPathDiffersFromTitle' = getFoamVsCodeConfig(
|
||||
'completion.useAlias'
|
||||
);
|
||||
return aliasStyle;
|
||||
}
|
||||
|
||||
const normalize = (text: string) => text.toLocaleLowerCase().trim();
|
||||
function wikilinkRequiresAlias(resource: Resource) {
|
||||
return normalize(resource.uri.getName()) !== normalize(resource.title);
|
||||
}
|
||||
|
||||
export default feature;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { createTestNote, createTestWorkspace } from '../test/test-utils';
|
||||
import { isOrphan } from './orphans';
|
||||
|
||||
const orphanA = createTestNote({
|
||||
uri: '/path/orphan-a.md',
|
||||
title: 'Orphan A',
|
||||
});
|
||||
|
||||
const nonOrphan1 = createTestNote({
|
||||
uri: '/path/non-orphan-1.md',
|
||||
});
|
||||
|
||||
const nonOrphan2 = createTestNote({
|
||||
uri: '/path/non-orphan-2.md',
|
||||
links: [{ slug: 'non-orphan-1' }],
|
||||
});
|
||||
|
||||
const workspace = createTestWorkspace()
|
||||
.set(orphanA)
|
||||
.set(nonOrphan1)
|
||||
.set(nonOrphan2);
|
||||
const graph = FoamGraph.fromWorkspace(workspace);
|
||||
|
||||
describe('isOrphan', () => {
|
||||
it('should return true when a note with no connections is provided', () => {
|
||||
expect(isOrphan(orphanA.uri, graph)).toBeTruthy();
|
||||
});
|
||||
it('should return false when a note with connections is provided', () => {
|
||||
expect(isOrphan(nonOrphan1.uri, graph)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { getOrphansConfig } from '../settings';
|
||||
import { FoamFeature } from '../types';
|
||||
import {
|
||||
GroupedResourcesTreeDataProvider,
|
||||
ResourceTreeItem,
|
||||
UriTreeItem,
|
||||
} from '../utils/grouped-resources-tree-data-provider';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
context: vscode.ExtensionContext,
|
||||
foamPromise: Promise<Foam>
|
||||
) => {
|
||||
const foam = await foamPromise;
|
||||
|
||||
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
|
||||
fromVsCodeUri(dir.uri)
|
||||
);
|
||||
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'orphans',
|
||||
'orphan',
|
||||
getOrphansConfig(),
|
||||
workspacesURIs,
|
||||
() => foam.graph.getAllNodes().filter(uri => isOrphan(uri, foam.graph)),
|
||||
uri => {
|
||||
if (uri.isPlaceholder()) {
|
||||
return new UriTreeItem(uri);
|
||||
}
|
||||
const resource = foam.workspace.find(uri);
|
||||
return new ResourceTreeItem(resource, foam.workspace);
|
||||
}
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('foam-vscode.orphans', provider),
|
||||
...provider.commands,
|
||||
foam.graph.onDidUpdate(() => provider.refresh())
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const isOrphan = (uri: URI, graph: FoamGraph) =>
|
||||
graph.getConnections(uri).length === 0;
|
||||
|
||||
export default feature;
|
||||
@@ -1,17 +1,17 @@
|
||||
import { workspace, window } from 'vscode';
|
||||
import { createTestNote, createTestWorkspace } from '../test/test-utils';
|
||||
import { createTestNote, createTestWorkspace } from '../../test/test-utils';
|
||||
import {
|
||||
cleanWorkspace,
|
||||
closeEditors,
|
||||
createNote,
|
||||
getUriInWorkspace,
|
||||
} from '../test/test-utils-vscode';
|
||||
} from '../../test/test-utils-vscode';
|
||||
import { BacklinksTreeDataProvider, BacklinkTreeItem } from './backlinks';
|
||||
import { ResourceTreeItem } from '../utils/grouped-resources-tree-data-provider';
|
||||
import { OPEN_COMMAND } from './commands/open-resource';
|
||||
import { toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { ResourceTreeItem } from '../../utils/grouped-resources-tree-data-provider';
|
||||
import { OPEN_COMMAND } from '../commands/open-resource';
|
||||
import { toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { FoamGraph } from '../../core/model/graph';
|
||||
import { URI } from '../../core/model/uri';
|
||||
|
||||
describe('Backlinks panel', () => {
|
||||
beforeAll(async () => {
|
||||
@@ -1,16 +1,16 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { groupBy } from 'lodash';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { URI } from '../../core/model/uri';
|
||||
|
||||
import { getNoteTooltip, isNone } from '../utils';
|
||||
import { FoamFeature } from '../types';
|
||||
import { ResourceTreeItem } from '../utils/grouped-resources-tree-data-provider';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { FoamGraph } from '../core/model/graph';
|
||||
import { Resource, ResourceLink } from '../core/model/note';
|
||||
import { Range } from '../core/model/range';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { getNoteTooltip, isNone } from '../../utils';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { ResourceTreeItem } from '../../utils/grouped-resources-tree-data-provider';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { FoamGraph } from '../../core/model/graph';
|
||||
import { Resource, ResourceLink } from '../../core/model/note';
|
||||
import { Range } from '../../core/model/range';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../../utils/vsc-utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { TextDecoder } from 'util';
|
||||
import { getGraphStyle, getTitleMaxLength } from '../settings';
|
||||
import { isSome } from '../utils';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { Logger } from '../core/utils/log';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
import { getGraphStyle, getTitleMaxLength } from '../../settings';
|
||||
import { isSome } from '../../utils';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { Logger } from '../../core/utils/log';
|
||||
import { fromVsCodeUri } from '../../utils/vsc-utils';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: (context: vscode.ExtensionContext, foamPromise: Promise<Foam>) => {
|
||||
@@ -141,10 +141,11 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
|
||||
const selectedNote = foam.workspace.get(fromVsCodeUri(noteUri));
|
||||
|
||||
if (isSome(selectedNote)) {
|
||||
const doc = await vscode.workspace.openTextDocument(
|
||||
selectedNote.uri.path // vscode doesn't recognize the URI directly
|
||||
vscode.commands.executeCommand(
|
||||
'vscode.open',
|
||||
noteUri,
|
||||
vscode.ViewColumn.One
|
||||
);
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
|
||||
}
|
||||
break;
|
||||
}
|
||||
5
packages/foam-vscode/src/features/panels/index.ts
Normal file
5
packages/foam-vscode/src/features/panels/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { default as backlinks } from './backlinks';
|
||||
export { default as dataviz } from './dataviz';
|
||||
export { default as orphans } from './orphans';
|
||||
export { default as placeholders } from './placeholders';
|
||||
export { default as tags } from './tags-explorer';
|
||||
46
packages/foam-vscode/src/features/panels/orphans.ts
Normal file
46
packages/foam-vscode/src/features/panels/orphans.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { createMatcherAndDataStore } from '../../services/editor';
|
||||
import { getOrphansConfig } from '../../settings';
|
||||
import { FoamFeature } from '../../types';
|
||||
import {
|
||||
GroupedResourcesTreeDataProvider,
|
||||
ResourceTreeItem,
|
||||
UriTreeItem,
|
||||
} from '../../utils/grouped-resources-tree-data-provider';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
context: vscode.ExtensionContext,
|
||||
foamPromise: Promise<Foam>
|
||||
) => {
|
||||
const foam = await foamPromise;
|
||||
|
||||
const { matcher } = await createMatcherAndDataStore(
|
||||
getOrphansConfig().exclude
|
||||
);
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'orphans',
|
||||
'orphan',
|
||||
() =>
|
||||
foam.graph
|
||||
.getAllNodes()
|
||||
.filter(uri => foam.graph.getConnections(uri).length === 0),
|
||||
uri => {
|
||||
return uri.isPlaceholder()
|
||||
? new UriTreeItem(uri)
|
||||
: new ResourceTreeItem(foam.workspace.find(uri), foam.workspace);
|
||||
},
|
||||
matcher
|
||||
);
|
||||
provider.setGroupBy(getOrphansConfig().groupBy);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('foam-vscode.orphans', provider),
|
||||
...provider.commands,
|
||||
foam.graph.onDidUpdate(() => provider.refresh())
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default feature;
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { getPlaceholdersConfig } from '../settings';
|
||||
import { FoamFeature } from '../types';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { createMatcherAndDataStore } from '../../services/editor';
|
||||
import { getPlaceholdersConfig } from '../../settings';
|
||||
import { FoamFeature } from '../../types';
|
||||
import {
|
||||
GroupedResourcesTreeDataProvider,
|
||||
UriTreeItem,
|
||||
} from '../utils/grouped-resources-tree-data-provider';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
} from '../../utils/grouped-resources-tree-data-provider';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -14,19 +14,19 @@ const feature: FoamFeature = {
|
||||
foamPromise: Promise<Foam>
|
||||
) => {
|
||||
const foam = await foamPromise;
|
||||
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
|
||||
fromVsCodeUri(dir.uri)
|
||||
const { matcher } = await createMatcherAndDataStore(
|
||||
getPlaceholdersConfig().exclude
|
||||
);
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'placeholders',
|
||||
'placeholder',
|
||||
getPlaceholdersConfig(),
|
||||
workspacesURIs,
|
||||
() => foam.graph.getAllNodes().filter(uri => uri.isPlaceholder()),
|
||||
uri => {
|
||||
return new UriTreeItem(uri);
|
||||
}
|
||||
},
|
||||
matcher
|
||||
);
|
||||
provider.setGroupBy(getPlaceholdersConfig().groupBy);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider(
|
||||
@@ -1,19 +1,24 @@
|
||||
import { createTestNote, readFileFromFs } from '../test/test-utils';
|
||||
import { cleanWorkspace, closeEditors } from '../test/test-utils-vscode';
|
||||
import { TagItem, TagReference, TagsProvider } from './tags-tree-view';
|
||||
import { bootstrap, Foam } from '../core/model/foam';
|
||||
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
|
||||
import { FileDataStore, Matcher } from '../core/services/datastore';
|
||||
import { createMarkdownParser } from '../core/services/markdown-parser';
|
||||
import {
|
||||
createTestNote,
|
||||
readFileFromFs,
|
||||
TEST_DATA_DIR,
|
||||
} from '../../test/test-utils';
|
||||
import { cleanWorkspace, closeEditors } from '../../test/test-utils-vscode';
|
||||
import { TagItem, TagReference, TagsProvider } from './tags-explorer';
|
||||
import { bootstrap, Foam } from '../../core/model/foam';
|
||||
import { MarkdownResourceProvider } from '../../core/services/markdown-provider';
|
||||
import { createMarkdownParser } from '../../core/services/markdown-parser';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import { FileDataStore, Matcher } from '../../test/test-datastore';
|
||||
|
||||
describe('Tags tree panel', () => {
|
||||
let _foam: Foam;
|
||||
let provider: TagsProvider;
|
||||
|
||||
const dataStore = new FileDataStore(readFileFromFs);
|
||||
const matcher = new Matcher([]);
|
||||
const dataStore = new FileDataStore(readFileFromFs, TEST_DATA_DIR.toFsPath());
|
||||
const matcher = new Matcher([URI.file(TEST_DATA_DIR.toFsPath())]);
|
||||
const parser = createMarkdownParser();
|
||||
const mdProvider = new MarkdownResourceProvider(matcher, dataStore, parser);
|
||||
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanWorkspace();
|
||||
@@ -25,7 +30,9 @@ describe('Tags tree panel', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
_foam = await bootstrap(matcher, dataStore, parser, [mdProvider]);
|
||||
_foam = await bootstrap(matcher, undefined, dataStore, parser, [
|
||||
mdProvider,
|
||||
]);
|
||||
provider = new TagsProvider(_foam, _foam.workspace);
|
||||
await closeEditors();
|
||||
});
|
||||
@@ -1,11 +1,11 @@
|
||||
import { URI } from '../core/model/uri';
|
||||
import { URI } from '../../core/model/uri';
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../types';
|
||||
import { getNoteTooltip, isSome } from '../utils';
|
||||
import { toVsCodeRange, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { Foam } from '../core/model/foam';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { Resource, Tag } from '../core/model/note';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { getNoteTooltip, isSome } from '../../utils';
|
||||
import { toVsCodeRange, toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { Resource, Tag } from '../../core/model/note';
|
||||
|
||||
const TAG_SEPARATOR = '/';
|
||||
const feature: FoamFeature = {
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import markdownItFoamTags from './tag-highlight';
|
||||
import markdownItWikilinkNavigation from './wikilink-navigation';
|
||||
import markdownItRemoveLinkReferences from './remove-wikilink-references';
|
||||
import markdownItWikilinkEmbed from './wikilink-embed';
|
||||
import { default as markdownItFoamTags } from './tag-highlight';
|
||||
import { default as markdownItWikilinkNavigation } from './wikilink-navigation';
|
||||
import { default as markdownItRemoveLinkReferences } from './remove-wikilink-references';
|
||||
import { default as markdownItWikilinkEmbed } from './wikilink-embed';
|
||||
|
||||
const feature: FoamFeature = {
|
||||
activate: async (
|
||||
@@ -16,9 +16,9 @@ const feature: FoamFeature = {
|
||||
return {
|
||||
extendMarkdownIt: (md: markdownit) => {
|
||||
return [
|
||||
markdownItWikilinkEmbed,
|
||||
markdownItFoamTags,
|
||||
markdownItWikilinkNavigation,
|
||||
markdownItWikilinkEmbed,
|
||||
markdownItRemoveLinkReferences,
|
||||
].reduce((acc, extension) => extension(acc, foam.workspace), md);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import markdownItFoamTags from './tag-highlight';
|
||||
import { default as markdownItFoamTags } from './tag-highlight';
|
||||
|
||||
describe('Stylable tag generation in preview', () => {
|
||||
const md = markdownItFoamTags(MarkdownIt(), new FoamWorkspace());
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
deleteFile,
|
||||
withModifiedFoamConfiguration,
|
||||
} from '../../test/test-utils-vscode';
|
||||
import markdownItWikilinkEmbed, {
|
||||
import {
|
||||
default as markdownItWikilinkEmbed,
|
||||
CONFIG_EMBED_NOTE_IN_CONTAINER,
|
||||
} from './wikilink-embed';
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import * as vscode from 'vscode';
|
||||
import { isSome } from '../../utils';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { Logger } from '../../core/utils/log';
|
||||
import { toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { Resource } from '../../core/model/note';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
@@ -17,7 +15,7 @@ export const markdownItWikilinkEmbed = (
|
||||
workspace: FoamWorkspace
|
||||
) => {
|
||||
return md.use(markdownItRegex, {
|
||||
name: 'include-notes',
|
||||
name: 'embed-wikilinks',
|
||||
regex: /!\[\[([^[\]]+?)\]\]/,
|
||||
replace: (wikilink: string) => {
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { createMarkdownParser } from '../../core/services/markdown-parser';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { createTestNote } from '../../test/test-utils';
|
||||
import { getUriInWorkspace } from '../../test/test-utils-vscode';
|
||||
import markdownItWikilinkNavigation from './wikilink-navigation';
|
||||
import markdownItRemoveLinkReferences from './remove-wikilink-references';
|
||||
|
||||
const parser = createMarkdownParser();
|
||||
import { default as markdownItWikilinkNavigation } from './wikilink-navigation';
|
||||
import { default as markdownItRemoveLinkReferences } from './remove-wikilink-references';
|
||||
|
||||
describe('Link generation in preview', () => {
|
||||
const noteA = createTestNote({
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import markdownItRegex from 'markdown-it-regex';
|
||||
import * as vscode from 'vscode';
|
||||
import { FoamFeature } from '../../types';
|
||||
import { isNone, isSome } from '../../utils';
|
||||
import { Foam } from '../../core/model/foam';
|
||||
import { isNone } from '../../utils';
|
||||
import { FoamWorkspace } from '../../core/model/workspace';
|
||||
import { Logger } from '../../core/utils/log';
|
||||
import { toVsCodeUri } from '../../utils/vsc-utils';
|
||||
import { Resource } from '../../core/model/note';
|
||||
import { MarkdownLink } from '../../core/services/markdown-link';
|
||||
import { Range } from '../../core/model/range';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { getFoamVsCodeConfig } from '../../services/config';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export const markdownItWikilinkNavigation = (
|
||||
md: markdownit,
|
||||
@@ -20,7 +14,7 @@ export const markdownItWikilinkNavigation = (
|
||||
) => {
|
||||
return md.use(markdownItRegex, {
|
||||
name: 'connect-wikilinks',
|
||||
regex: /\[\[([^[\]]+?)\]\]/,
|
||||
regex: /(?=[^!])\[\[([^[\]]+?)\]\]/,
|
||||
replace: (wikilink: string) => {
|
||||
try {
|
||||
const { target, alias } = MarkdownLink.analyzeLink({
|
||||
|
||||
@@ -5,7 +5,12 @@ import {
|
||||
createFile,
|
||||
showInEditor,
|
||||
} from '../test/test-utils-vscode';
|
||||
import { getCurrentEditorDirectory, replaceSelection } from './editor';
|
||||
import {
|
||||
asAbsoluteWorkspaceUri,
|
||||
getCurrentEditorDirectory,
|
||||
replaceSelection,
|
||||
} from './editor';
|
||||
import { URI } from '../core/model/uri';
|
||||
|
||||
describe('Editor utils', () => {
|
||||
beforeAll(closeEditors);
|
||||
@@ -38,4 +43,14 @@ describe('Editor utils', () => {
|
||||
expect(doc.doc.getText()).toEqual('This was the file A');
|
||||
});
|
||||
});
|
||||
|
||||
describe('asAbsoluteWorkspaceUri', () => {
|
||||
it('should work with the VS Code workspace folders if none are passed', () => {
|
||||
const uri = URI.file('relative/path');
|
||||
const workspaceFolder = workspace.workspaceFolders[0];
|
||||
expect(asAbsoluteWorkspaceUri(uri)).toEqual(
|
||||
fromVsCodeUri(workspaceFolder.uri).joinPath(uri.path)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { URI } from '../core/model/uri';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { asAbsoluteUri, URI } from '../core/model/uri';
|
||||
import { TextEncoder } from 'util';
|
||||
import {
|
||||
FileType,
|
||||
RelativePattern,
|
||||
Selection,
|
||||
SnippetString,
|
||||
TextDocument,
|
||||
Uri,
|
||||
ViewColumn,
|
||||
window,
|
||||
workspace,
|
||||
@@ -12,6 +16,13 @@ import {
|
||||
import { focusNote } from '../utils';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { isSome } from '../core/utils';
|
||||
import {
|
||||
AlwaysIncludeMatcher,
|
||||
FileListBasedMatcher,
|
||||
GenericDataStore,
|
||||
IDataStore,
|
||||
IMatcher,
|
||||
} from '../core/services/datastore';
|
||||
|
||||
interface SelectionInfo {
|
||||
document: TextDocument;
|
||||
@@ -85,3 +96,92 @@ export function getCurrentEditorDirectory(): URI {
|
||||
|
||||
throw new Error('A file must be open in editor, or workspace folder needed');
|
||||
}
|
||||
|
||||
export async function fileExists(uri: URI): Promise<boolean> {
|
||||
try {
|
||||
const stat = await workspace.fs.stat(toVsCodeUri(uri));
|
||||
return stat.type === FileType.File;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFile(uri: URI): Promise<string | undefined> {
|
||||
if (await fileExists(uri)) {
|
||||
return workspace.fs
|
||||
.readFile(toVsCodeUri(uri))
|
||||
.then(bytes => bytes.toString());
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const deleteFile = (uri: URI) => {
|
||||
return workspace.fs.delete(toVsCodeUri(uri), { recursive: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* Turns a relative URI into an absolute URI for the given workspace.
|
||||
* @param uri the uri to evaluate
|
||||
* @returns an absolute uri
|
||||
*/
|
||||
export function asAbsoluteWorkspaceUri(uri: URI): URI {
|
||||
if (workspace.workspaceFolders === undefined) {
|
||||
throw new Error('An open folder or workspace is required');
|
||||
}
|
||||
const folders = workspace.workspaceFolders.map(folder =>
|
||||
fromVsCodeUri(folder.uri)
|
||||
);
|
||||
const res = asAbsoluteUri(uri, folders);
|
||||
return res;
|
||||
}
|
||||
|
||||
export const createMatcherAndDataStore = async (
|
||||
excludes: string[]
|
||||
): Promise<{
|
||||
matcher: IMatcher;
|
||||
dataStore: IDataStore;
|
||||
excludePatterns: Map<string, string[]>;
|
||||
}> => {
|
||||
const excludePatterns = new Map<string, string[]>();
|
||||
workspace.workspaceFolders.forEach(f => excludePatterns.set(f.name, []));
|
||||
|
||||
for (const exclude of excludes) {
|
||||
const tokens = exclude.split('/');
|
||||
const matchesFolder = workspace.workspaceFolders.find(
|
||||
f => f.name === tokens[0]
|
||||
);
|
||||
if (matchesFolder) {
|
||||
excludePatterns.get(tokens[0]).push(tokens.slice(1).join('/'));
|
||||
} else {
|
||||
for (const [, value] of excludePatterns.entries()) {
|
||||
value.push(exclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listFiles = async () => {
|
||||
let files: Uri[] = [];
|
||||
for (const folder of workspace.workspaceFolders) {
|
||||
const uris = await workspace.findFiles(
|
||||
new RelativePattern(folder.uri.path, '**/*'),
|
||||
new RelativePattern(
|
||||
folder.uri.path,
|
||||
`{${excludePatterns.get(folder.name).join(',')}}`
|
||||
)
|
||||
);
|
||||
files = [...files, ...uris];
|
||||
}
|
||||
|
||||
return files.map(fromVsCodeUri);
|
||||
};
|
||||
|
||||
const readFile = async (uri: URI) =>
|
||||
(await workspace.fs.readFile(toVsCodeUri(uri))).toString();
|
||||
|
||||
const dataStore = new GenericDataStore(listFiles, readFile);
|
||||
const matcher = isEmpty(excludes)
|
||||
? new AlwaysIncludeMatcher()
|
||||
: await FileListBasedMatcher.createFromListFn(listFiles);
|
||||
|
||||
return { matcher, dataStore, excludePatterns };
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Selection, ViewColumn, window, workspace } from 'vscode';
|
||||
import { isWindows } from '../core/common/platform';
|
||||
import { Selection, ViewColumn, window } from 'vscode';
|
||||
import { fromVsCodeUri } from '../utils/vsc-utils';
|
||||
import { determineNewNoteFilepath, NoteFactory } from '../services/templates';
|
||||
import { NoteFactory } from '../services/templates';
|
||||
import {
|
||||
closeEditors,
|
||||
createFile,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
showInEditor,
|
||||
} from '../test/test-utils-vscode';
|
||||
import { Resolver } from './variable-resolver';
|
||||
import { fileExists } from './editor';
|
||||
|
||||
describe('Create note from template', () => {
|
||||
beforeEach(async () => {
|
||||
@@ -113,27 +113,6 @@ foam_template: # foam template metadata
|
||||
});
|
||||
|
||||
describe('Creation with active text selection', () => {
|
||||
it('should populate FOAM_SELECTED_TEXT with the current selection', async () => {
|
||||
const templateA = await createFile('Template A', [
|
||||
'.foam',
|
||||
'templates',
|
||||
'template-a.md',
|
||||
]);
|
||||
const file = await createFile('Content of first file');
|
||||
const { editor } = await showInEditor(file.uri);
|
||||
editor.selection = new Selection(0, 11, 1, 0);
|
||||
const target = getUriInWorkspace();
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
await NoteFactory.createFromTemplate(templateA.uri, resolver, target);
|
||||
expect(await resolver.resolveFromName('FOAM_SELECTED_TEXT')).toEqual(
|
||||
'first file'
|
||||
);
|
||||
|
||||
await deleteFile(templateA);
|
||||
await deleteFile(target);
|
||||
await deleteFile(file);
|
||||
});
|
||||
|
||||
it('should open created note in a new column if there was a selection', async () => {
|
||||
const templateA = await createFile('Template A', [
|
||||
'.foam',
|
||||
@@ -183,91 +162,69 @@ foam_template: # foam template metadata
|
||||
expect(window.visibleTextEditors[0].document.getText()).toEqual(
|
||||
`This is my first file: [[${target.getName()}]]`
|
||||
);
|
||||
await deleteFile(template.uri);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('determineNewNoteFilepath', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
describe('NoteFactory.createNote', () => {
|
||||
beforeEach(async () => {
|
||||
await closeEditors();
|
||||
});
|
||||
it('should use the template path if absolute', async () => {
|
||||
const winAbsolutePath = 'C:\\absolute_path\\journal\\My Note Title.md';
|
||||
const linuxAbsolutePath = '/absolute_path/journal/My Note Title.md';
|
||||
const winResult = await determineNewNoteFilepath(
|
||||
winAbsolutePath,
|
||||
undefined,
|
||||
it('should create a new note', async () => {
|
||||
const target = getUriInWorkspace();
|
||||
await NoteFactory.createNote(
|
||||
target,
|
||||
'Hello World',
|
||||
new Resolver(new Map(), new Date())
|
||||
);
|
||||
expect(winResult.toFsPath()).toMatch(winAbsolutePath);
|
||||
const linuxResult = await determineNewNoteFilepath(
|
||||
linuxAbsolutePath,
|
||||
undefined,
|
||||
new Resolver(new Map(), new Date())
|
||||
);
|
||||
expect(linuxResult.toFsPath()).toMatch(linuxAbsolutePath);
|
||||
expect(await fileExists(target)).toBeTruthy();
|
||||
expect(window.activeTextEditor.document.getText()).toEqual('Hello World');
|
||||
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('should compute the relative template filepath from the current directory', async () => {
|
||||
const relativePath = isWindows
|
||||
? 'journal\\My Note Title.md'
|
||||
: 'journal/My Note Title.md';
|
||||
const resultFilepath = await determineNewNoteFilepath(
|
||||
relativePath,
|
||||
it('should support not replacing the selection with a link to the newly created note', async () => {
|
||||
const file = await createFile('This is my first file: World');
|
||||
const { editor } = await showInEditor(file.uri);
|
||||
editor.selection = new Selection(0, 23, 0, 28);
|
||||
const target = getUriInWorkspace();
|
||||
await NoteFactory.createNote(
|
||||
target,
|
||||
'Hello ${FOAM_SELECTED_TEXT} ${FOAM_SELECTED_TEXT}', // eslint-disable-line no-template-curly-in-string
|
||||
new Resolver(new Map(), new Date()),
|
||||
undefined,
|
||||
new Resolver(new Map(), new Date())
|
||||
false
|
||||
);
|
||||
const expectedPath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(relativePath);
|
||||
expect(resultFilepath.toFsPath()).toMatch(expectedPath.toFsPath());
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'Hello World World'
|
||||
);
|
||||
expect(window.visibleTextEditors[0].document.getText()).toEqual(
|
||||
`This is my first file: World`
|
||||
);
|
||||
await deleteFile(file.uri);
|
||||
await deleteFile(target);
|
||||
});
|
||||
|
||||
it('should use the note title if nothing else is available', async () => {
|
||||
const noteTitle = 'My new note';
|
||||
const resultFilepath = await determineNewNoteFilepath(
|
||||
it('should support replacing the selection with a link to the newly created note', async () => {
|
||||
const file = await createFile('This is my first file: World');
|
||||
const { editor } = await showInEditor(file.uri);
|
||||
editor.selection = new Selection(0, 23, 0, 28);
|
||||
const target = getUriInWorkspace();
|
||||
await NoteFactory.createNote(
|
||||
target,
|
||||
'Hello ${FOAM_SELECTED_TEXT} ${FOAM_SELECTED_TEXT}', // eslint-disable-line no-template-curly-in-string
|
||||
new Resolver(new Map(), new Date()),
|
||||
undefined,
|
||||
undefined,
|
||||
new Resolver(new Map().set('FOAM_TITLE', noteTitle), new Date())
|
||||
true
|
||||
);
|
||||
const expectedPath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(`${noteTitle}.md`);
|
||||
expect(resultFilepath.toFsPath()).toMatch(expectedPath.toFsPath());
|
||||
});
|
||||
|
||||
it('should ask the user for a note title if nothing else is available', async () => {
|
||||
const noteTitle = 'My new note';
|
||||
const spy = jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(noteTitle)));
|
||||
const resultFilepath = await determineNewNoteFilepath(
|
||||
undefined,
|
||||
undefined,
|
||||
new Resolver(new Map(), new Date())
|
||||
expect(window.activeTextEditor.document.getText()).toEqual(
|
||||
'Hello World World'
|
||||
);
|
||||
const expectedPath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(`${noteTitle}.md`);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(resultFilepath.toFsPath()).toMatch(expectedPath.toFsPath());
|
||||
});
|
||||
|
||||
it('should filter invalid chars from the title #1042', async () => {
|
||||
const noteTitle = 'My new note/';
|
||||
const spy = jest
|
||||
.spyOn(window, 'showInputBox')
|
||||
.mockImplementationOnce(jest.fn(() => Promise.resolve(noteTitle)));
|
||||
const resultFilepath = await determineNewNoteFilepath(
|
||||
undefined,
|
||||
undefined,
|
||||
new Resolver(new Map(), new Date())
|
||||
expect(window.visibleTextEditors[0].document.getText()).toEqual(
|
||||
`This is my first file: [[${target.getName()}]]`
|
||||
);
|
||||
const expectedPath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(`My new note.md`);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(resultFilepath.toFsPath()).toMatch(expectedPath.toFsPath());
|
||||
await deleteFile(file.uri);
|
||||
await deleteFile(target);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +1,51 @@
|
||||
import { URI } from '../core/model/uri';
|
||||
import { TextEncoder } from 'util';
|
||||
import { FileType, SnippetString, ViewColumn, window, workspace } from 'vscode';
|
||||
import {
|
||||
SnippetString,
|
||||
ViewColumn,
|
||||
QuickPickItem,
|
||||
commands,
|
||||
window,
|
||||
workspace,
|
||||
} from 'vscode';
|
||||
import { focusNote } from '../utils';
|
||||
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
|
||||
import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser';
|
||||
import { UserCancelledOperation } from './errors';
|
||||
import {
|
||||
asAbsoluteWorkspaceUri,
|
||||
createDocAndFocus,
|
||||
deleteFile,
|
||||
fileExists,
|
||||
findSelectionContent,
|
||||
getCurrentEditorDirectory,
|
||||
readFile,
|
||||
replaceSelection,
|
||||
} from './editor';
|
||||
import { Resolver } from './variable-resolver';
|
||||
import dateFormat from 'dateformat';
|
||||
import { isSome } from '../core/utils';
|
||||
|
||||
/**
|
||||
* The templates directory
|
||||
*/
|
||||
export const TEMPLATES_DIR = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath('.foam', 'templates');
|
||||
export const getTemplatesDir = () =>
|
||||
fromVsCodeUri(workspace.workspaceFolders[0].uri).joinPath(
|
||||
'.foam',
|
||||
'templates'
|
||||
);
|
||||
|
||||
/**
|
||||
* The URI of the default template
|
||||
*/
|
||||
export const DEFAULT_TEMPLATE_URI = TEMPLATES_DIR.joinPath('new-note.md');
|
||||
export const getDefaultTemplateUri = () =>
|
||||
getTemplatesDir().joinPath('new-note.md');
|
||||
|
||||
/**
|
||||
* The URI of the template for daily notes
|
||||
*/
|
||||
export const DAILY_NOTE_TEMPLATE_URI = TEMPLATES_DIR.joinPath('daily-note.md');
|
||||
export const getDailyNoteTemplateUri = () =>
|
||||
getTemplatesDir().joinPath('daily-note.md');
|
||||
|
||||
const WIKILINK_DEFAULT_TEMPLATE_TEXT = `# $\{1:$FOAM_TITLE}\n\n$0`;
|
||||
|
||||
@@ -55,9 +71,7 @@ For a full list of features see [the VS Code snippets page](https://code.visuals
|
||||
export async function getTemplateMetadata(
|
||||
templateUri: URI
|
||||
): Promise<Map<string, string>> {
|
||||
const contents = await workspace.fs
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString());
|
||||
const contents = (await readFile(templateUri)) ?? '';
|
||||
const [templateMetadata] = extractFoamTemplateFrontmatterMetadata(contents);
|
||||
return templateMetadata;
|
||||
}
|
||||
@@ -74,11 +88,7 @@ export async function getTemplateInfo(
|
||||
templateFallbackText = '',
|
||||
resolver: Resolver
|
||||
) {
|
||||
const templateText = (await fileExists(templateUri))
|
||||
? await workspace.fs
|
||||
.readFile(toVsCodeUri(templateUri))
|
||||
.then(bytes => bytes.toString())
|
||||
: templateFallbackText;
|
||||
const templateText = (await readFile(templateUri)) ?? templateFallbackText;
|
||||
|
||||
const templateWithResolvedVariables = await resolver.resolveText(
|
||||
templateText
|
||||
@@ -95,7 +105,173 @@ export async function getTemplateInfo(
|
||||
};
|
||||
}
|
||||
|
||||
export type OnFileExistStrategy =
|
||||
| 'open'
|
||||
| 'overwrite'
|
||||
| 'cancel'
|
||||
| 'ask'
|
||||
| ((filePath: URI) => Promise<URI | undefined>);
|
||||
|
||||
export async function askUserForTemplate() {
|
||||
const templates = await getTemplates();
|
||||
if (templates.length === 0) {
|
||||
return offerToCreateTemplate();
|
||||
}
|
||||
|
||||
const templatesMetadata = (
|
||||
await Promise.all(
|
||||
templates.map(async templateUri => {
|
||||
const metadata = await getTemplateMetadata(templateUri);
|
||||
metadata.set('templatePath', templateUri.getBasename());
|
||||
return metadata;
|
||||
})
|
||||
)
|
||||
).sort(sortTemplatesMetadata);
|
||||
|
||||
const items: QuickPickItem[] = await Promise.all(
|
||||
templatesMetadata.map(metadata => {
|
||||
const label = metadata.get('name') || metadata.get('templatePath');
|
||||
const description = metadata.get('name')
|
||||
? metadata.get('templatePath')
|
||||
: null;
|
||||
const detail = metadata.get('description');
|
||||
const item = {
|
||||
label: label,
|
||||
description: description,
|
||||
detail: detail,
|
||||
};
|
||||
Object.keys(item).forEach(key => {
|
||||
if (!item[key]) {
|
||||
delete item[key];
|
||||
}
|
||||
});
|
||||
return item;
|
||||
})
|
||||
);
|
||||
|
||||
const selectedTemplate = await window.showQuickPick(items, {
|
||||
placeHolder: 'Select a template to use.',
|
||||
});
|
||||
|
||||
if (selectedTemplate === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const templateFilename =
|
||||
(selectedTemplate as QuickPickItem).description ||
|
||||
(selectedTemplate as QuickPickItem).label;
|
||||
const templateUri = getTemplatesDir().joinPath(templateFilename);
|
||||
return templateUri;
|
||||
}
|
||||
|
||||
async function offerToCreateTemplate(): Promise<void> {
|
||||
const response = await window.showQuickPick(['Yes', 'No'], {
|
||||
placeHolder:
|
||||
'No templates available. Would you like to create one instead?',
|
||||
});
|
||||
if (response === 'Yes') {
|
||||
commands.executeCommand('foam-vscode.create-new-template');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function sortTemplatesMetadata(
|
||||
t1: Map<string, string>,
|
||||
t2: Map<string, string>
|
||||
) {
|
||||
// Sort by name's existence, then name, then path
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (t1.get('name') !== undefined && t2.get('name') === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const pathSortOrder = t1
|
||||
.get('templatePath')
|
||||
.localeCompare(t2.get('templatePath'));
|
||||
|
||||
if (t1.get('name') === undefined && t2.get('name') === undefined) {
|
||||
return pathSortOrder;
|
||||
}
|
||||
|
||||
const nameSortOrder = t1.get('name').localeCompare(t2.get('name'));
|
||||
|
||||
return nameSortOrder || pathSortOrder;
|
||||
}
|
||||
|
||||
export const NoteFactory = {
|
||||
createNote: async (
|
||||
newFilePath: URI,
|
||||
text: string,
|
||||
resolver: Resolver,
|
||||
onFileExists?: OnFileExistStrategy,
|
||||
replaceSelectionWithLink = true
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
try {
|
||||
const onFileExistsFn = async (existingFile: URI) => {
|
||||
if (typeof onFileExists === 'function') {
|
||||
return onFileExists(existingFile);
|
||||
}
|
||||
switch (onFileExists) {
|
||||
case 'open':
|
||||
await commands.executeCommand(
|
||||
'vscode.open',
|
||||
toVsCodeUri(existingFile)
|
||||
);
|
||||
return;
|
||||
case 'overwrite':
|
||||
await deleteFile(existingFile);
|
||||
return existingFile;
|
||||
case 'cancel':
|
||||
return undefined;
|
||||
case 'ask':
|
||||
default: {
|
||||
const newProposedPath = await askUserForFilepathConfirmation(
|
||||
existingFile
|
||||
);
|
||||
return newProposedPath && URI.file(newProposedPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (await fileExists(newFilePath)) {
|
||||
const proposedNewFilepath = await onFileExistsFn(newFilePath);
|
||||
|
||||
if (proposedNewFilepath === undefined) {
|
||||
return { didCreateFile: false, uri: newFilePath };
|
||||
}
|
||||
newFilePath = proposedNewFilepath;
|
||||
}
|
||||
|
||||
const expandedText = await resolver.resolveText(text);
|
||||
const selectedContent = findSelectionContent();
|
||||
await createDocAndFocus(
|
||||
new SnippetString(expandedText),
|
||||
newFilePath,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
);
|
||||
|
||||
if (replaceSelectionWithLink && selectedContent !== undefined) {
|
||||
const newNoteTitle = newFilePath.getName();
|
||||
|
||||
await replaceSelection(
|
||||
selectedContent.document,
|
||||
selectedContent.selection,
|
||||
`[[${newNoteTitle}]]`
|
||||
);
|
||||
}
|
||||
|
||||
return { didCreateFile: true, uri: newFilePath };
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new note using a template.
|
||||
* @param templateUri the URI of the template to use.
|
||||
@@ -108,60 +284,29 @@ export const NoteFactory = {
|
||||
resolver: Resolver,
|
||||
filepathFallbackURI?: URI,
|
||||
templateFallbackText = '',
|
||||
onFileExists?: (filePath: URI) => Promise<string | undefined>
|
||||
onFileExists?: OnFileExistStrategy
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
try {
|
||||
onFileExists = onFileExists
|
||||
? onFileExists
|
||||
: (existingFile: URI) => {
|
||||
const filename = existingFile.getBasename();
|
||||
return askUserForFilepathConfirmation(existingFile, filename);
|
||||
};
|
||||
|
||||
const template = await getTemplateInfo(
|
||||
templateUri,
|
||||
templateFallbackText,
|
||||
resolver
|
||||
);
|
||||
|
||||
const selectedContent = findSelectionContent();
|
||||
if (selectedContent?.content) {
|
||||
resolver.define('FOAM_SELECTED_TEXT', selectedContent?.content);
|
||||
}
|
||||
|
||||
const templateSnippet = new SnippetString(template.text);
|
||||
|
||||
let newFilePath = await determineNewNoteFilepath(
|
||||
template.metadata.get('filepath'),
|
||||
filepathFallbackURI,
|
||||
resolver
|
||||
const newFilePath = asAbsoluteWorkspaceUri(
|
||||
template.metadata.has('filepath')
|
||||
? URI.file(template.metadata.get('filepath'))
|
||||
: isSome(filepathFallbackURI)
|
||||
? filepathFallbackURI
|
||||
: await getPathFromTitle(resolver)
|
||||
);
|
||||
while (await fileExists(newFilePath)) {
|
||||
const proposedNewFilepath = await onFileExists(newFilePath);
|
||||
|
||||
if (proposedNewFilepath === undefined) {
|
||||
return { didCreateFile: false, uri: newFilePath };
|
||||
}
|
||||
newFilePath = URI.file(proposedNewFilepath);
|
||||
}
|
||||
|
||||
await createDocAndFocus(
|
||||
templateSnippet,
|
||||
return NoteFactory.createNote(
|
||||
newFilePath,
|
||||
selectedContent ? ViewColumn.Beside : ViewColumn.Active
|
||||
template.text,
|
||||
resolver,
|
||||
onFileExists
|
||||
);
|
||||
|
||||
if (selectedContent !== undefined) {
|
||||
const newNoteTitle = newFilePath.getName();
|
||||
|
||||
await replaceSelection(
|
||||
selectedContent.document,
|
||||
selectedContent.selection,
|
||||
`[[${newNoteTitle}]]`
|
||||
);
|
||||
}
|
||||
|
||||
return { didCreateFile: true, uri: newFilePath };
|
||||
} catch (err) {
|
||||
if (err instanceof UserCancelledOperation) {
|
||||
return;
|
||||
@@ -185,7 +330,7 @@ export const NoteFactory = {
|
||||
targetDate
|
||||
);
|
||||
return NoteFactory.createFromTemplate(
|
||||
DAILY_NOTE_TEMPLATE_URI,
|
||||
getDailyNoteTemplateUri(),
|
||||
resolver,
|
||||
filepathFallbackURI,
|
||||
templateFallbackText,
|
||||
@@ -197,17 +342,24 @@ export const NoteFactory = {
|
||||
* Creates a new note when following a placeholder wikilink using the default template.
|
||||
* @param wikilinkPlaceholder the placeholder value from the wikilink. (eg. `[[Hello Joe]]` -> `Hello Joe`)
|
||||
* @param filepathFallbackURI the URI to use if the template does not specify the `filepath` metadata attribute. This is configurable by the caller for backwards compatibility purposes.
|
||||
* @param templateURI URI of the template to use. If undefined, use the default template.
|
||||
*/
|
||||
createForPlaceholderWikilink: (
|
||||
createForPlaceholderWikilink: async (
|
||||
wikilinkPlaceholder: string,
|
||||
filepathFallbackURI: URI
|
||||
filepathFallbackURI: URI,
|
||||
templateURI?: URI
|
||||
): Promise<{ didCreateFile: boolean; uri: URI | undefined }> => {
|
||||
const resolver = new Resolver(
|
||||
new Map().set('FOAM_TITLE', wikilinkPlaceholder),
|
||||
new Date()
|
||||
);
|
||||
|
||||
if (templateURI === undefined) {
|
||||
templateURI = getDefaultTemplateUri();
|
||||
}
|
||||
|
||||
return NoteFactory.createFromTemplate(
|
||||
DEFAULT_TEMPLATE_URI,
|
||||
templateURI,
|
||||
resolver,
|
||||
filepathFallbackURI,
|
||||
WIKILINK_DEFAULT_TEMPLATE_TEXT
|
||||
@@ -217,7 +369,7 @@ export const NoteFactory = {
|
||||
|
||||
export const createTemplate = async (): Promise<void> => {
|
||||
const defaultFilename = 'new-template.md';
|
||||
const defaultTemplate = TEMPLATES_DIR.joinPath(defaultFilename);
|
||||
const defaultTemplate = getTemplatesDir().joinPath(defaultFilename);
|
||||
const fsPath = defaultTemplate.toFsPath();
|
||||
const filename = await window.showInputBox({
|
||||
prompt: `Enter the filename for the new template`,
|
||||
@@ -243,14 +395,18 @@ export const createTemplate = async (): Promise<void> => {
|
||||
};
|
||||
|
||||
async function askUserForFilepathConfirmation(
|
||||
defaultFilepath: URI,
|
||||
defaultFilename: string
|
||||
) {
|
||||
defaultFilepath: URI
|
||||
): Promise<string | undefined> {
|
||||
const fsPath = defaultFilepath.toFsPath();
|
||||
return await window.showInputBox({
|
||||
const defaultFilename = defaultFilepath.getBasename();
|
||||
const defaultExtension = defaultFilepath.getExtension();
|
||||
return window.showInputBox({
|
||||
prompt: `Enter the filename for the new note`,
|
||||
value: fsPath,
|
||||
valueSelection: [fsPath.length - defaultFilename.length, fsPath.length - 3],
|
||||
valueSelection: [
|
||||
fsPath.length - defaultFilename.length,
|
||||
fsPath.length - defaultExtension.length,
|
||||
],
|
||||
validateInput: async value =>
|
||||
value.trim().length === 0
|
||||
? 'Please enter a value'
|
||||
@@ -271,25 +427,14 @@ async function askUserForFilepathConfirmation(
|
||||
*/
|
||||
const UNALLOWED_CHARS = '/\\#%&{}<>?*$!\'":@+`|=';
|
||||
|
||||
export async function determineNewNoteFilepath(
|
||||
templateFilepathAttribute: string | undefined,
|
||||
fallbackURI: URI | undefined,
|
||||
resolver: Resolver
|
||||
): Promise<URI> {
|
||||
if (templateFilepathAttribute) {
|
||||
let defaultFilepath = URI.file(templateFilepathAttribute);
|
||||
if (!defaultFilepath.isAbsolute()) {
|
||||
defaultFilepath = fromVsCodeUri(
|
||||
workspace.workspaceFolders[0].uri
|
||||
).joinPath(templateFilepathAttribute);
|
||||
}
|
||||
return defaultFilepath;
|
||||
}
|
||||
|
||||
if (fallbackURI) {
|
||||
return fallbackURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the title to generate a file path.
|
||||
* It sanitizes the title to remove special characters and spaces.
|
||||
*
|
||||
* @param resolver the resolver to use
|
||||
* @returns the string path of the new note
|
||||
*/
|
||||
export const getPathFromTitle = async (resolver: Resolver) => {
|
||||
let defaultName = await resolver.resolveFromName('FOAM_TITLE');
|
||||
UNALLOWED_CHARS.split('').forEach(char => {
|
||||
defaultName = defaultName.split(char).join('');
|
||||
@@ -299,13 +444,4 @@ export async function determineNewNoteFilepath(
|
||||
`${defaultName}.md`
|
||||
);
|
||||
return defaultFilepath;
|
||||
}
|
||||
|
||||
async function fileExists(uri: URI): Promise<boolean> {
|
||||
try {
|
||||
const stat = await workspace.fs.stat(toVsCodeUri(uri));
|
||||
return stat.type === FileType.File;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { window } from 'vscode';
|
||||
import { Selection, window } from 'vscode';
|
||||
import { Resolver } from './variable-resolver';
|
||||
import { Variable } from '../core/common/snippetParser';
|
||||
import {
|
||||
createFile,
|
||||
deleteFile,
|
||||
showInEditor,
|
||||
} from '../test/test-utils-vscode';
|
||||
|
||||
describe('substituteFoamVariables', () => {
|
||||
test('Does nothing if no Foam-specific variables are used', async () => {
|
||||
describe('variable-resolver, text substitution', () => {
|
||||
it('should do nothing if no Foam-specific variables are used', async () => {
|
||||
const input = `
|
||||
# \${AnotherVariable} <-- Unrelated to Foam
|
||||
# \${AnotherVariable:default_value} <-- Unrelated to Foam
|
||||
@@ -31,7 +36,7 @@ describe('substituteFoamVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(input);
|
||||
});
|
||||
|
||||
test('Correctly substitutes variables that are substrings of one another', async () => {
|
||||
it('should correctly substitute variables that are substrings of one another', async () => {
|
||||
// FOAM_TITLE is a substring of FOAM_TITLE_NON_EXISTENT_VARIABLE
|
||||
// If we're not careful with how we substitute the values
|
||||
// we can end up putting the FOAM_TITLE in place FOAM_TITLE_NON_EXISTENT_VARIABLE should be.
|
||||
@@ -56,8 +61,8 @@ describe('substituteFoamVariables', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveFoamVariables', () => {
|
||||
test('Does nothing for unknown Foam-specific variables', async () => {
|
||||
describe('variable-resolver, variable resolution', () => {
|
||||
it('should do nothing for unknown Foam-specific variables', async () => {
|
||||
const variables = [new Variable('FOAM_FOO')];
|
||||
|
||||
const expected = new Map<string, string>();
|
||||
@@ -67,7 +72,7 @@ describe('resolveFoamVariables', () => {
|
||||
expect(await resolver.resolveAll(variables)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Resolves FOAM_TITLE', async () => {
|
||||
it('should resolve FOAM_TITLE', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
const variables = [new Variable('FOAM_TITLE'), new Variable('FOAM_SLUG')];
|
||||
|
||||
@@ -84,7 +89,7 @@ describe('resolveFoamVariables', () => {
|
||||
expect(await resolver.resolveAll(variables)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Resolves FOAM_TITLE without asking the user when it is provided', async () => {
|
||||
it('should resolve FOAM_TITLE without asking the user when it is provided', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
const variables = [new Variable('FOAM_TITLE')];
|
||||
|
||||
@@ -97,7 +102,7 @@ describe('resolveFoamVariables', () => {
|
||||
expect(await resolver.resolveAll(variables)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Resolves FOAM_DATE_* properties with current day by default', async () => {
|
||||
it('should resolve FOAM_DATE_* properties with current day by default', async () => {
|
||||
const variables = [
|
||||
new Variable('FOAM_DATE_YEAR'),
|
||||
new Variable('FOAM_DATE_YEAR_SHORT'),
|
||||
@@ -134,7 +139,7 @@ describe('resolveFoamVariables', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Resolves FOAM_DATE_* properties with given date', async () => {
|
||||
it('should resolve FOAM_DATE_* properties with given date', async () => {
|
||||
const targetDate = new Date(2021, 9, 12, 1, 2, 3);
|
||||
const variables = [
|
||||
new Variable('FOAM_DATE_YEAR'),
|
||||
@@ -149,6 +154,7 @@ describe('resolveFoamVariables', () => {
|
||||
new Variable('FOAM_DATE_MINUTE'),
|
||||
new Variable('FOAM_DATE_SECOND'),
|
||||
new Variable('FOAM_DATE_SECONDS_UNIX'),
|
||||
new Variable('FOAM_DATE_WEEK'),
|
||||
];
|
||||
|
||||
const expected = new Map<string, string>();
|
||||
@@ -161,6 +167,7 @@ describe('resolveFoamVariables', () => {
|
||||
expected.set('FOAM_DATE_DAY_NAME', 'Tuesday');
|
||||
expected.set('FOAM_DATE_DAY_NAME_SHORT', 'Tue');
|
||||
expected.set('FOAM_DATE_HOUR', '01');
|
||||
expected.set('FOAM_DATE_WEEK', '41');
|
||||
expected.set('FOAM_DATE_MINUTE', '02');
|
||||
expected.set('FOAM_DATE_SECOND', '03');
|
||||
expected.set(
|
||||
@@ -173,10 +180,35 @@ describe('resolveFoamVariables', () => {
|
||||
|
||||
expect(await resolver.resolveAll(variables)).toEqual(expected);
|
||||
});
|
||||
|
||||
describe('FOAM_DATE_WEEK', () => {
|
||||
it('should start counting weeks from 1', async () => {
|
||||
// week number starts from 1, not 0
|
||||
// the first "partial week" of the year is really the last of the previous
|
||||
const resolver = new Resolver(
|
||||
new Map<string, string>(),
|
||||
new Date(2021, 0, 1, 1, 2, 3)
|
||||
);
|
||||
expect(await resolver.resolve(new Variable('FOAM_DATE_WEEK'))).toEqual(
|
||||
'53'
|
||||
);
|
||||
});
|
||||
|
||||
it('should pad week number to 2 digits', async () => {
|
||||
// week number is 2-digit
|
||||
const resolver = new Resolver(
|
||||
new Map<string, string>(),
|
||||
new Date(2021, 0, 7, 1, 2, 3)
|
||||
);
|
||||
expect(await resolver.resolve(new Variable('FOAM_DATE_WEEK'))).toEqual(
|
||||
'01'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveFoamTemplateVariables', () => {
|
||||
test('Does nothing for template without Foam-specific variables', async () => {
|
||||
describe('variable-resolver, resolveText', () => {
|
||||
it('should do nothing for template without Foam-specific variables', async () => {
|
||||
const input = `
|
||||
# \${AnotherVariable} <-- Unrelated to Foam
|
||||
# \${AnotherVariable:default_value} <-- Unrelated to Foam
|
||||
@@ -191,7 +223,7 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Does nothing for unknown Foam-specific variables', async () => {
|
||||
it('should do nothing for unknown Foam-specific variables', async () => {
|
||||
const input = `
|
||||
# $FOAM_FOO
|
||||
# \${FOAM_FOO}
|
||||
@@ -204,7 +236,18 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in a newline', async () => {
|
||||
it('should resolve FOAM_SELECTED_TEXT with the editor selection', async () => {
|
||||
const file = await createFile('Content of note file');
|
||||
const { editor } = await showInEditor(file.uri);
|
||||
editor.selection = new Selection(0, 11, 1, 0);
|
||||
const resolver = new Resolver(new Map(), new Date());
|
||||
expect(await resolver.resolveFromName('FOAM_SELECTED_TEXT')).toEqual(
|
||||
'note file'
|
||||
);
|
||||
await deleteFile(file);
|
||||
});
|
||||
|
||||
it('should append FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in a newline', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
@@ -221,7 +264,7 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in multiple newlines', async () => {
|
||||
it('should append FOAM_SELECTED_TEXT with a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template ends in multiple newlines', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
@@ -238,7 +281,7 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Appends FOAM_SELECTED_TEXT without a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template does not end in a newline', async () => {
|
||||
it('should append FOAM_SELECTED_TEXT without a newline to the template if there is selected text but FOAM_SELECTED_TEXT is not referenced and the template does not end in a newline', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
@@ -255,7 +298,7 @@ describe('resolveFoamTemplateVariables', () => {
|
||||
expect(await resolver.resolveText(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Does not append FOAM_SELECTED_TEXT to a template if there is no selected text and is not referenced', async () => {
|
||||
it('should not append FOAM_SELECTED_TEXT to a template if there is no selected text and is not referenced', async () => {
|
||||
const foamTitle = 'My note title';
|
||||
|
||||
jest
|
||||
|
||||
@@ -18,6 +18,7 @@ const knownFoamVariables = new Set([
|
||||
'FOAM_DATE_MONTH_NAME',
|
||||
'FOAM_DATE_MONTH_NAME_SHORT',
|
||||
'FOAM_DATE_DATE',
|
||||
'FOAM_DATE_WEEK',
|
||||
'FOAM_DATE_DAY_NAME',
|
||||
'FOAM_DATE_DAY_NAME_SHORT',
|
||||
'FOAM_DATE_HOUR',
|
||||
@@ -165,6 +166,25 @@ export class Resolver implements VariableResolver {
|
||||
String(this.foamDate.getDate().valueOf()).padStart(2, '0')
|
||||
);
|
||||
break;
|
||||
case 'FOAM_DATE_WEEK': {
|
||||
// https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
||||
const date = new Date(this.foamDate);
|
||||
|
||||
// Find Thursday of this week starting on Monday
|
||||
date.setDate(date.getDate() + 4 - (date.getDay() || 7));
|
||||
const thursday = date.getTime();
|
||||
|
||||
// Find January 1st
|
||||
date.setMonth(0); // January
|
||||
date.setDate(1); // 1st
|
||||
const janFirst = date.getTime();
|
||||
|
||||
// Round the amount of days to compensate for daylight saving time
|
||||
const days = Math.round((thursday - janFirst) / 86400000); // 1 day = 86400000 ms
|
||||
const weekDay = Math.floor(days / 7) + 1;
|
||||
value = Promise.resolve(String(weekDay.valueOf()).padStart(2, '0'));
|
||||
break;
|
||||
}
|
||||
case 'FOAM_DATE_DAY_NAME':
|
||||
value = Promise.resolve(
|
||||
this.foamDate.toLocaleString('default', { weekday: 'long' })
|
||||
|
||||
85
packages/foam-vscode/src/test/test-datastore.test.ts
Normal file
85
packages/foam-vscode/src/test/test-datastore.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { URI } from '../core/model/uri';
|
||||
import { Logger } from '../core/utils/log';
|
||||
import { Matcher, toMatcherPathFormat } from './test-datastore';
|
||||
import { TEST_DATA_DIR } from './test-utils';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const testFolder = TEST_DATA_DIR.joinPath('test-datastore');
|
||||
|
||||
describe('Matcher', () => {
|
||||
it('generates globs with the base dir provided', () => {
|
||||
const matcher = new Matcher([testFolder], ['*'], []);
|
||||
expect(matcher.folders).toEqual([toMatcherPathFormat(testFolder)]);
|
||||
expect(matcher.include).toEqual([
|
||||
toMatcherPathFormat(testFolder.joinPath('*')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults to including everything and excluding nothing', () => {
|
||||
const matcher = new Matcher([testFolder]);
|
||||
expect(matcher.exclude).toEqual([]);
|
||||
expect(matcher.include).toEqual([
|
||||
toMatcherPathFormat(testFolder.joinPath('**', '*')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('supports multiple includes', () => {
|
||||
const matcher = new Matcher([testFolder], ['g1', 'g2'], []);
|
||||
expect(matcher.exclude).toEqual([]);
|
||||
expect(matcher.include).toEqual([
|
||||
toMatcherPathFormat(testFolder.joinPath('g1')),
|
||||
toMatcherPathFormat(testFolder.joinPath('g2')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('has a match method to filter strings', () => {
|
||||
const matcher = new Matcher([testFolder], ['*.md'], []);
|
||||
const files = [
|
||||
testFolder.joinPath('file1.md'),
|
||||
testFolder.joinPath('file2.md'),
|
||||
testFolder.joinPath('file3.mdx'),
|
||||
testFolder.joinPath('sub', 'file4.md'),
|
||||
];
|
||||
expect(matcher.match(files)).toEqual([
|
||||
testFolder.joinPath('file1.md'),
|
||||
testFolder.joinPath('file2.md'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('has a isMatch method to see whether a file is matched or not', () => {
|
||||
const matcher = new Matcher([testFolder], ['*.md'], []);
|
||||
const files = [
|
||||
testFolder.joinPath('file1.md'),
|
||||
testFolder.joinPath('file2.md'),
|
||||
testFolder.joinPath('file3.mdx'),
|
||||
testFolder.joinPath('sub', 'file4.md'),
|
||||
];
|
||||
expect(matcher.isMatch(files[0])).toEqual(true);
|
||||
expect(matcher.isMatch(files[1])).toEqual(true);
|
||||
expect(matcher.isMatch(files[2])).toEqual(false);
|
||||
expect(matcher.isMatch(files[3])).toEqual(false);
|
||||
});
|
||||
|
||||
it('happy path', () => {
|
||||
const matcher = new Matcher([URI.file('/root/')], ['**/*'], ['**/*.pdf']);
|
||||
expect(matcher.isMatch(URI.file('/root/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/root/file.pdf'))).toBeFalsy();
|
||||
expect(matcher.isMatch(URI.file('/root/dir/file.md'))).toBeTruthy();
|
||||
expect(matcher.isMatch(URI.file('/root/dir/file.pdf'))).toBeFalsy();
|
||||
});
|
||||
|
||||
it('ignores files in the exclude list', () => {
|
||||
const matcher = new Matcher([testFolder], ['*.md'], ['file1.*']);
|
||||
const files = [
|
||||
testFolder.joinPath('file1.md'),
|
||||
testFolder.joinPath('file2.md'),
|
||||
testFolder.joinPath('file3.mdx'),
|
||||
testFolder.joinPath('sub', 'file4.md'),
|
||||
];
|
||||
expect(matcher.isMatch(files[0])).toEqual(false);
|
||||
expect(matcher.isMatch(files[1])).toEqual(true);
|
||||
expect(matcher.isMatch(files[2])).toEqual(false);
|
||||
expect(matcher.isMatch(files[3])).toEqual(false);
|
||||
});
|
||||
});
|
||||
96
packages/foam-vscode/src/test/test-datastore.ts
Normal file
96
packages/foam-vscode/src/test/test-datastore.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import micromatch from 'micromatch';
|
||||
import { promisify } from 'util';
|
||||
import { glob } from 'glob';
|
||||
import { Logger } from '../core/utils/log';
|
||||
import { IDataStore, IMatcher } from '../core/services/datastore';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { isWindows } from '../core/common/platform';
|
||||
import { asAbsolutePaths } from '../core/utils/path';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
|
||||
/**
|
||||
* File system based data store
|
||||
*/
|
||||
export class FileDataStore implements IDataStore {
|
||||
constructor(
|
||||
private readFile: (uri: URI) => Promise<string>,
|
||||
private readonly basedir: string
|
||||
) {}
|
||||
|
||||
async list(): Promise<URI[]> {
|
||||
const res = await findAllFiles([this.basedir, '**/*'].join('/'));
|
||||
return res.map(URI.file);
|
||||
}
|
||||
|
||||
async read(uri: URI) {
|
||||
try {
|
||||
return await this.readFile(uri);
|
||||
} catch (e) {
|
||||
Logger.error(
|
||||
`FileDataStore: error while reading uri: ${uri.path} - ${e}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The matcher requires the path to be in unix format, so if we are in windows
|
||||
* we convert the fs path on the way in and out
|
||||
*/
|
||||
export const toMatcherPathFormat = isWindows
|
||||
? (uri: URI) => uri.toFsPath().replace(/\\/g, '/')
|
||||
: (uri: URI) => uri.toFsPath();
|
||||
|
||||
export const toFsPath = isWindows
|
||||
? (path: string): string => path.replace(/\//g, '\\')
|
||||
: (path: string): string => path;
|
||||
|
||||
export class Matcher implements IMatcher {
|
||||
public readonly folders: string[];
|
||||
public readonly include: string[] = [];
|
||||
public readonly exclude: string[] = [];
|
||||
|
||||
constructor(
|
||||
baseFolders: URI[],
|
||||
includeGlobs: string[] = ['**/*'],
|
||||
excludeGlobs: string[] = []
|
||||
) {
|
||||
this.folders = baseFolders.map(toMatcherPathFormat);
|
||||
Logger.info('Workspace folders: ', this.folders);
|
||||
|
||||
this.include = includeGlobs.flatMap(glob =>
|
||||
asAbsolutePaths(glob, this.folders)
|
||||
);
|
||||
this.exclude = excludeGlobs.flatMap(glob =>
|
||||
asAbsolutePaths(glob, this.folders)
|
||||
);
|
||||
|
||||
Logger.info('Glob patterns', {
|
||||
includeGlobs: this.include,
|
||||
ignoreGlobs: this.exclude,
|
||||
});
|
||||
}
|
||||
|
||||
match(files: URI[]) {
|
||||
const matches = micromatch(
|
||||
files.map(f => f.toFsPath()),
|
||||
this.include,
|
||||
{
|
||||
ignore: this.exclude,
|
||||
nocase: true,
|
||||
format: toFsPath,
|
||||
}
|
||||
);
|
||||
return matches.map(URI.file);
|
||||
}
|
||||
|
||||
isMatch(uri: URI) {
|
||||
return this.match([uri]).length > 0;
|
||||
}
|
||||
|
||||
refresh(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ Logger.setLevel('error');
|
||||
|
||||
export const cleanWorkspace = async () => {
|
||||
const files = await vscode.workspace.findFiles('**', '{.vscode,.keep}');
|
||||
await Promise.all(files.map(f => vscode.workspace.fs.delete(f)));
|
||||
await Promise.all(files.map(f => deleteFile(fromVsCodeUri(f))));
|
||||
};
|
||||
|
||||
export const showInEditor = async (uri: URI) => {
|
||||
@@ -28,9 +28,15 @@ export const closeEditors = async () => {
|
||||
await wait(100);
|
||||
};
|
||||
|
||||
export const deleteFile = (file: URI | { uri: URI }) => {
|
||||
export const deleteFile = async (file: URI | { uri: URI }) => {
|
||||
const uri = 'uri' in file ? file.uri : file;
|
||||
return vscode.workspace.fs.delete(toVsCodeUri(uri), { recursive: true });
|
||||
try {
|
||||
await vscode.workspace.fs.delete(toVsCodeUri(uri), {
|
||||
recursive: true,
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -118,3 +124,21 @@ export const withModifiedConfiguration = async (key, value, fn: () => void) => {
|
||||
*/
|
||||
export const withModifiedFoamConfiguration = (key, value, fn: () => void) =>
|
||||
withModifiedConfiguration(`foam.${key}`, value, fn);
|
||||
|
||||
/**
|
||||
* Utility function to check if two URIs are the same.
|
||||
* It has the goal of supporting Uri and URI, and dealing with
|
||||
* inconsistencies in the way they are represented (especially the
|
||||
* drive letter in Windows)
|
||||
*
|
||||
* @param actual the actual value
|
||||
* @param expected the expected value
|
||||
*/
|
||||
export const expectSameUri = (
|
||||
actual: vscode.Uri | URI,
|
||||
expected: vscode.Uri | URI
|
||||
) => {
|
||||
expect(actual.path.toLocaleLowerCase()).toEqual(
|
||||
expected.path.toLocaleLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Logger } from '../core/utils/log';
|
||||
import { Range } from '../core/model/range';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { Matcher } from '../core/services/datastore';
|
||||
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
|
||||
import { NoteLinkDefinition, Resource } from '../core/model/note';
|
||||
import { createMarkdownParser } from '../core/services/markdown-parser';
|
||||
@@ -32,13 +31,11 @@ export const strToUri = URI.file;
|
||||
|
||||
export const createTestWorkspace = () => {
|
||||
const workspace = new FoamWorkspace();
|
||||
const matcher = new Matcher([URI.file('/')], ['**/*']);
|
||||
const parser = createMarkdownParser();
|
||||
const provider = new MarkdownResourceProvider(
|
||||
matcher,
|
||||
{
|
||||
read: _ => Promise.resolve(''),
|
||||
list: _ => Promise.resolve([]),
|
||||
list: () => Promise.resolve([]),
|
||||
},
|
||||
parser
|
||||
);
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
workspace,
|
||||
Selection,
|
||||
MarkdownString,
|
||||
version,
|
||||
ViewColumn,
|
||||
} from 'vscode';
|
||||
import matter from 'gray-matter';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
import { toVsCodeUri } from './utils/vsc-utils';
|
||||
import { Logger } from './core/utils/log';
|
||||
import { URI } from './core/model/uri';
|
||||
@@ -179,14 +177,8 @@ export function getContainsTooltip(titles: string[]): string {
|
||||
* @param note A Foam Note
|
||||
*/
|
||||
export function getNoteTooltip(content: string): string {
|
||||
const STABLE_MARKDOWN_STRING_API_VERSION = '1.52.1';
|
||||
const strippedContent = stripFrontMatter(stripImages(content));
|
||||
|
||||
if (version >= STABLE_MARKDOWN_STRING_API_VERSION) {
|
||||
return formatMarkdownTooltip(strippedContent) as any;
|
||||
}
|
||||
|
||||
return formatSimpleTooltip(strippedContent);
|
||||
return formatMarkdownTooltip(strippedContent) as any;
|
||||
}
|
||||
|
||||
export function formatMarkdownTooltip(content: string): MarkdownString {
|
||||
@@ -200,16 +192,6 @@ export function formatMarkdownTooltip(content: string): MarkdownString {
|
||||
return md;
|
||||
}
|
||||
|
||||
export function formatSimpleTooltip(content: string): string {
|
||||
const CHARACTERS_LIMIT = 200;
|
||||
const flatContent = removeMarkdown(content)
|
||||
.replace(/\r?\n|\r/g, ' ')
|
||||
.replace(/\s+/g, ' ');
|
||||
const extract = flatContent.substr(0, CHARACTERS_LIMIT);
|
||||
const ellipsis = flatContent.length > CHARACTERS_LIMIT ? '...' : '';
|
||||
return `${extract}${ellipsis}`;
|
||||
}
|
||||
|
||||
export function getExcerpt(
|
||||
markdown: string,
|
||||
maxLines: number
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { OPEN_COMMAND } from '../features/commands/open-resource';
|
||||
import {
|
||||
GroupedResoucesConfigGroupBy,
|
||||
GroupedResourcesConfig,
|
||||
} from '../settings';
|
||||
import { createTestNote, strToUri } from '../test/test-utils';
|
||||
AlwaysIncludeMatcher,
|
||||
SubstringExcludeMatcher,
|
||||
} from '../core/services/datastore';
|
||||
import { OPEN_COMMAND } from '../features/commands/open-resource';
|
||||
import { GroupedResoucesConfigGroupBy } from '../settings';
|
||||
import { createTestNote } from '../test/test-utils';
|
||||
import {
|
||||
DirectoryTreeItem,
|
||||
GroupedResourcesTreeDataProvider,
|
||||
UriTreeItem,
|
||||
} from './grouped-resources-tree-data-provider';
|
||||
|
||||
const testMatcher = new SubstringExcludeMatcher('path-exclude');
|
||||
|
||||
describe('GroupedResourcesTreeDataProvider', () => {
|
||||
const matchingNote1 = createTestNote({ uri: '/path/ABC.md', title: 'ABC' });
|
||||
const matchingNote2 = createTestNote({
|
||||
@@ -32,25 +35,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
.set(excludedPathNote)
|
||||
.set(notMatchingNote);
|
||||
|
||||
// Mock config
|
||||
const config: GroupedResourcesConfig = {
|
||||
exclude: ['path-exclude/**/*'],
|
||||
groupBy: GroupedResoucesConfigGroupBy.Folder,
|
||||
};
|
||||
|
||||
it('should return the grouped resources as a folder tree', async () => {
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'length3',
|
||||
'note',
|
||||
config,
|
||||
[strToUri('')],
|
||||
() =>
|
||||
workspace
|
||||
.list()
|
||||
.filter(r => r.title.length === 3)
|
||||
.map(r => r.uri),
|
||||
uri => new UriTreeItem(uri)
|
||||
uri => new UriTreeItem(uri),
|
||||
testMatcher
|
||||
);
|
||||
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
|
||||
const result = await provider.getChildren();
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
@@ -72,15 +69,16 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'length3',
|
||||
'note',
|
||||
config,
|
||||
[strToUri('')],
|
||||
() =>
|
||||
workspace
|
||||
.list()
|
||||
.filter(r => r.title.length === 3)
|
||||
.map(r => r.uri),
|
||||
uri => new UriTreeItem(uri)
|
||||
uri => new UriTreeItem(uri),
|
||||
testMatcher
|
||||
);
|
||||
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
|
||||
|
||||
const directory = new DirectoryTreeItem(
|
||||
'/path',
|
||||
[new UriTreeItem(matchingNote1.uri)],
|
||||
@@ -98,22 +96,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
});
|
||||
|
||||
it('should return the flattened resources', async () => {
|
||||
const mockConfig = {
|
||||
...config,
|
||||
groupBy: GroupedResoucesConfigGroupBy.Off,
|
||||
};
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'length3',
|
||||
'note',
|
||||
mockConfig,
|
||||
[strToUri('')],
|
||||
() =>
|
||||
workspace
|
||||
.list()
|
||||
.filter(r => r.title.length === 3)
|
||||
.map(r => r.uri),
|
||||
uri => new UriTreeItem(uri)
|
||||
uri => new UriTreeItem(uri),
|
||||
testMatcher
|
||||
);
|
||||
provider.setGroupBy(GroupedResoucesConfigGroupBy.Off);
|
||||
|
||||
const result = await provider.getChildren();
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
@@ -132,19 +127,19 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
});
|
||||
|
||||
it('should return the grouped resources without exclusion', async () => {
|
||||
const mockConfig = { ...config, exclude: [] };
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'length3',
|
||||
'note',
|
||||
mockConfig,
|
||||
[strToUri('')],
|
||||
() =>
|
||||
workspace
|
||||
.list()
|
||||
.filter(r => r.title.length === 3)
|
||||
.map(r => r.uri),
|
||||
uri => new UriTreeItem(uri)
|
||||
uri => new UriTreeItem(uri),
|
||||
new AlwaysIncludeMatcher()
|
||||
);
|
||||
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
|
||||
|
||||
const result = await provider.getChildren();
|
||||
expect(result).toMatchObject([
|
||||
expect.anything(),
|
||||
@@ -163,15 +158,15 @@ describe('GroupedResourcesTreeDataProvider', () => {
|
||||
const provider = new GroupedResourcesTreeDataProvider(
|
||||
'length3',
|
||||
description,
|
||||
config,
|
||||
[strToUri('')],
|
||||
() =>
|
||||
workspace
|
||||
.list()
|
||||
.filter(r => r.title.length === 3)
|
||||
.map(r => r.uri),
|
||||
uri => new UriTreeItem(uri)
|
||||
uri => new UriTreeItem(uri),
|
||||
testMatcher
|
||||
);
|
||||
provider.setGroupBy(GroupedResoucesConfigGroupBy.Folder);
|
||||
const result = await provider.getChildren();
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import micromatch from 'micromatch';
|
||||
import {
|
||||
GroupedResourcesConfig,
|
||||
GroupedResoucesConfigGroupBy,
|
||||
} from '../settings';
|
||||
import { GroupedResoucesConfigGroupBy } from '../settings';
|
||||
import { getContainsTooltip, getNoteTooltip, isSome } from '../utils';
|
||||
import { OPEN_COMMAND } from '../features/commands/open-resource';
|
||||
import { toVsCodeUri } from './vsc-utils';
|
||||
import { URI } from '../core/model/uri';
|
||||
import { Resource } from '../core/model/note';
|
||||
import { FoamWorkspace } from '../core/model/workspace';
|
||||
import { IMatcher } from '../core/services/datastore';
|
||||
|
||||
/**
|
||||
* Provides the ability to expose a TreeDataExplorerView in VSCode. This class will
|
||||
@@ -82,13 +79,10 @@ export class GroupedResourcesTreeDataProvider
|
||||
constructor(
|
||||
private providerId: string,
|
||||
private resourceName: string,
|
||||
config: GroupedResourcesConfig,
|
||||
workspaceUris: URI[],
|
||||
private computeResources: () => Array<URI>,
|
||||
private createTreeItem: (item: URI) => GroupedResourceTreeItem
|
||||
private createTreeItem: (item: URI) => GroupedResourceTreeItem,
|
||||
private matcher: IMatcher
|
||||
) {
|
||||
this.groupBy = config.groupBy;
|
||||
this.exclude = this.getGlobs(workspaceUris, config.exclude);
|
||||
this.setContext();
|
||||
this.doComputeResources();
|
||||
}
|
||||
@@ -163,30 +157,10 @@ export class GroupedResourcesTreeDataProvider
|
||||
|
||||
private doComputeResources(): void {
|
||||
this.flatUris = this.computeResources()
|
||||
.filter(uri => !this.isMatch(uri))
|
||||
.filter(uri => this.matcher.isMatch(uri))
|
||||
.filter(isSome);
|
||||
}
|
||||
|
||||
private isMatch(uri: URI) {
|
||||
return micromatch.isMatch(uri.toFsPath(), this.exclude);
|
||||
}
|
||||
|
||||
private getGlobs(fsURI: URI[], globs: string[]): string[] {
|
||||
globs = globs.map(glob => (glob.startsWith('/') ? glob.slice(1) : glob));
|
||||
|
||||
const exclude: string[] = [];
|
||||
|
||||
for (const fsPath of fsURI) {
|
||||
let folder = fsPath.path.replace(/\\/g, '/');
|
||||
if (folder.substr(-1) === '/') {
|
||||
folder = folder.slice(0, -1);
|
||||
}
|
||||
exclude.push(...globs.map(g => `${folder}/${g}`));
|
||||
}
|
||||
|
||||
return exclude;
|
||||
}
|
||||
|
||||
private getUrisByDirectory(): UrisByDirectory {
|
||||
const resourcesByDirectory: UrisByDirectory = {};
|
||||
for (const uri of this.flatUris) {
|
||||
|
||||
@@ -85,6 +85,8 @@ let model = {
|
||||
style: defaultStyle,
|
||||
showNodesOfType: {
|
||||
placeholder: true,
|
||||
image: false,
|
||||
attachment: false,
|
||||
note: true,
|
||||
tag: true,
|
||||
},
|
||||
@@ -235,12 +237,10 @@ function initDataviz(channel) {
|
||||
Actions.highlightNode(node?.id);
|
||||
})
|
||||
.onNodeClick((node, event) => {
|
||||
if (event.getModifierState('Control') || event.getModifierState('Meta')) {
|
||||
channel.postMessage({
|
||||
type: 'webviewDidSelectNode',
|
||||
payload: node.id,
|
||||
});
|
||||
}
|
||||
channel.postMessage({
|
||||
type: 'webviewDidSelectNode',
|
||||
payload: node.id,
|
||||
});
|
||||
Actions.selectNode(node.id, event.getModifierState('Shift'));
|
||||
})
|
||||
.onBackgroundClick(event => {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Welcome to your VS Code Extension
|
||||
|
||||
## What's in the folder
|
||||
|
||||
* This folder contains all of the files necessary for your extension.
|
||||
* `package.json` - this is the manifest file in which you declare your extension and command.
|
||||
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
|
||||
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
|
||||
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
|
||||
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
|
||||
|
||||
## Get up and running straight away
|
||||
|
||||
* Press `F5` to open a new window with your extension loaded.
|
||||
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
|
||||
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
|
||||
* Find output from your extension in the debug console.
|
||||
|
||||
## Make changes
|
||||
|
||||
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
|
||||
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
||||
|
||||
## Explore the API
|
||||
|
||||
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
|
||||
|
||||
## Run tests
|
||||
|
||||
* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
|
||||
* Press `F5` to run the tests in a new window with your extension loaded.
|
||||
* See the output of the test result in the debug console.
|
||||
* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
|
||||
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
|
||||
* You can create folders inside the `test` folder to structure your tests any way you want.
|
||||
|
||||
## Go further
|
||||
|
||||
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
|
||||
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
|
||||
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
|
||||
269
readme.md
269
readme.md
@@ -5,7 +5,7 @@
|
||||
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[](https://foambubble.github.io/join-discord/g)
|
||||
@@ -91,7 +91,7 @@ See for each occurrence the context in which it lives, as well as a preview of t
|
||||
|
||||
### Tag Explorer Panel
|
||||
|
||||
Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/user/user/features/tags).
|
||||
Tag your notes and navigate them with the [Tag Explorer](https://foambubble.github.io/foam/user/features/tags).
|
||||
Foam also supports hierarchical tags.
|
||||
|
||||

|
||||
@@ -197,135 +197,142 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt=""/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt=""/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt=""/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt=""/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt=""/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt=""/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt=""/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt=""/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt=""/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt=""/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://leonh.fr/"><img src="https://avatars.githubusercontent.com/u/19996318?v=4?s=60" width="60px;" alt=""/><br /><sub><b>léon h</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=leonhfr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nygaard.site"><img src="https://avatars.githubusercontent.com/u/4606342?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Nikhil Nygaard</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=njnygaard" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nitwit.se"><img src="https://avatars.githubusercontent.com/u/1382124?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mark Dixon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nitwit-se" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/joeltjames"><img src="https://avatars.githubusercontent.com/u/3732400?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joel James</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joeltjames" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.ryo33.com"><img src="https://avatars.githubusercontent.com/u/8780513?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hashiguchi Ryo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ryo33" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://movermeyer.com"><img src="https://avatars.githubusercontent.com/u/1459385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Michael Overmeyer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=movermeyer" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/derrickqin"><img src="https://avatars.githubusercontent.com/u/3038111?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Derrick Qin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=derrickqin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/zomars/"><img src="https://avatars.githubusercontent.com/u/3504472?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Omar López</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=zomars" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://robincn.com"><img src="https://avatars.githubusercontent.com/u/1583193?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Robin King</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=RobinKing" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/deegovee"><img src="https://avatars.githubusercontent.com/u/4730170?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Dheepak </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dheepakg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Pearcekieser"><img src="https://avatars.githubusercontent.com/u/5055971?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Pearcekieser</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Pearcekieser" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theowenyoung"><img src="https://avatars.githubusercontent.com/u/62473795?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Owen Young</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=theowenyoung" title="Documentation">📖</a> <a href="#content-theowenyoung" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt=""/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt=""/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iam-yan"><img src="https://avatars.githubusercontent.com/u/48427014?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yan</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=iam-yan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://WikiEducator.org/User:JimTittsler"><img src="https://avatars.githubusercontent.com/u/180326?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jim Tittsler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://malcolmmielle.wordpress.com/"><img src="https://avatars.githubusercontent.com/u/4457840?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Malcolm Mielle</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MalcolmMielle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://snippets.page/"><img src="https://avatars.githubusercontent.com/u/74916913?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Veesar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=veesar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bentongxyz"><img src="https://avatars.githubusercontent.com/u/60358804?v=4?s=60" width="60px;" alt=""/><br /><sub><b>bentongxyz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bentongxyz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianjdevries.com"><img src="https://avatars.githubusercontent.com/u/42778030?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Brian DeVries</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=techCarpenter" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.drewtyler.com"><img src="https://avatars.githubusercontent.com/u/5640816?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Drew Tyler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=drewtyler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Lauviah0622"><img src="https://avatars.githubusercontent.com/u/43416399?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Lauviah0622</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lauviah0622" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.elastic.co/elastic-agent"><img src="https://avatars.githubusercontent.com/u/1813008?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Josh Dover</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joshdover" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://phelm.co.uk"><img src="https://avatars.githubusercontent.com/u/4057948?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Phil Helm</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=phelma" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lingyv-li"><img src="https://avatars.githubusercontent.com/u/8937944?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Larry Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lingyv-li" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="https://jevakallio.dev/"><img src="https://avatars1.githubusercontent.com/u/1203949?v=4?s=60" width="60px;" alt="Jani Eväkallio"/><br /><sub><b>Jani Eväkallio</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jevakallio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://joeprevite.com/"><img src="https://avatars3.githubusercontent.com/u/3806031?v=4?s=60" width="60px;" alt="Joe Previte"/><br /><sub><b>Joe Previte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jsjoeio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/riccardoferretti"><img src="https://avatars3.githubusercontent.com/u/457005?v=4?s=60" width="60px;" alt="Riccardo"/><br /><sub><b>Riccardo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=riccardoferretti" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://ojanaho.com/"><img src="https://avatars0.githubusercontent.com/u/2180090?v=4?s=60" width="60px;" alt="Janne Ojanaho"/><br /><sub><b>Janne Ojanaho</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jojanaho" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://bypaulshen.com/"><img src="https://avatars3.githubusercontent.com/u/2266187?v=4?s=60" width="60px;" alt="Paul Shen"/><br /><sub><b>Paul Shen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=paulshen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/coffenbacher"><img src="https://avatars0.githubusercontent.com/u/245867?v=4?s=60" width="60px;" alt="coffenbacher"/><br /><sub><b>coffenbacher</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=coffenbacher" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mathieu.dutour.me/"><img src="https://avatars2.githubusercontent.com/u/3254314?v=4?s=60" width="60px;" alt="Mathieu Dutour"/><br /><sub><b>Mathieu Dutour</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=mathieudutour" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/presidentelect"><img src="https://avatars2.githubusercontent.com/u/1242300?v=4?s=60" width="60px;" alt="Michael Hansen"/><br /><sub><b>Michael Hansen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=presidentelect" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://klickverbot.at/"><img src="https://avatars1.githubusercontent.com/u/19335?v=4?s=60" width="60px;" alt="David Nadlinger"/><br /><sub><b>David Nadlinger</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dnadlinger" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pluckd.co/"><img src="https://avatars2.githubusercontent.com/u/20598571?v=4?s=60" width="60px;" alt="Fernando"/><br /><sub><b>Fernando</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MrCordeiro" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jfgonzalez7"><img src="https://avatars3.githubusercontent.com/u/58857736?v=4?s=60" width="60px;" alt="Juan Gonzalez"/><br /><sub><b>Juan Gonzalez</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jfgonzalez7" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.louiechristie.com/"><img src="https://avatars1.githubusercontent.com/u/6807448?v=4?s=60" width="60px;" alt="Louie Christie"/><br /><sub><b>Louie Christie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=louiechristie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://supersandro.de/"><img src="https://avatars2.githubusercontent.com/u/7258858?v=4?s=60" width="60px;" alt="Sandro"/><br /><sub><b>Sandro</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SuperSandro2000" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Skn0tt"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4?s=60" width="60px;" alt="Simon Knott"/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Skn0tt" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://styfle.dev/"><img src="https://avatars1.githubusercontent.com/u/229881?v=4?s=60" width="60px;" alt="Steven"/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=styfle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Georift"><img src="https://avatars2.githubusercontent.com/u/859430?v=4?s=60" width="60px;" alt="Tim"/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Georift" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sauravkhdoolia"><img src="https://avatars1.githubusercontent.com/u/34188267?v=4?s=60" width="60px;" alt="Saurav Khdoolia"/><br /><sub><b>Saurav Khdoolia</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sauravkhdoolia" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://anku.netlify.com/"><img src="https://avatars1.githubusercontent.com/u/22813027?v=4?s=60" width="60px;" alt="Ankit Tiwari"/><br /><sub><b>Ankit Tiwari</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anku255" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Tests">⚠️</a> <a href="https://github.com/foambubble/foam/commits?author=anku255" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ayushbaweja"><img src="https://avatars1.githubusercontent.com/u/44344063?v=4?s=60" width="60px;" alt="Ayush Baweja"/><br /><sub><b>Ayush Baweja</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ayushbaweja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/TaiChi-IO"><img src="https://avatars3.githubusercontent.com/u/65092992?v=4?s=60" width="60px;" alt="TaiChi-IO"/><br /><sub><b>TaiChi-IO</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=TaiChi-IO" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/juanfrank77"><img src="https://avatars1.githubusercontent.com/u/12146882?v=4?s=60" width="60px;" alt="Juan F Gonzalez "/><br /><sub><b>Juan F Gonzalez </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=juanfrank77" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://sanketdg.github.io"><img src="https://avatars3.githubusercontent.com/u/8980971?v=4?s=60" width="60px;" alt="Sanket Dasgupta"/><br /><sub><b>Sanket Dasgupta</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Documentation">📖</a> <a href="https://github.com/foambubble/foam/commits?author=SanketDG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstafie"><img src="https://avatars1.githubusercontent.com/u/10801854?v=4?s=60" width="60px;" alt="Nicholas Stafie"/><br /><sub><b>Nicholas Stafie</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nstafie" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/francishamel"><img src="https://avatars3.githubusercontent.com/u/36383308?v=4?s=60" width="60px;" alt="Francis Hamel"/><br /><sub><b>Francis Hamel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=francishamel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://digiguru.co.uk"><img src="https://avatars1.githubusercontent.com/u/619436?v=4?s=60" width="60px;" alt="digiguru"/><br /><sub><b>digiguru</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=digiguru" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/chirag-singhal"><img src="https://avatars3.githubusercontent.com/u/42653703?v=4?s=60" width="60px;" alt="CHIRAG SINGHAL"/><br /><sub><b>CHIRAG SINGHAL</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chirag-singhal" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lostintangent"><img src="https://avatars3.githubusercontent.com/u/116461?v=4?s=60" width="60px;" alt="Jonathan Carter"/><br /><sub><b>Jonathan Carter</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lostintangent" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.synesthesia.co.uk"><img src="https://avatars3.githubusercontent.com/u/181399?v=4?s=60" width="60px;" alt="Julian Elve"/><br /><sub><b>Julian Elve</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=synesthesia" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/thomaskoppelaar"><img src="https://avatars3.githubusercontent.com/u/36331365?v=4?s=60" width="60px;" alt="Thomas Koppelaar"/><br /><sub><b>Thomas Koppelaar</b></sub></a><br /><a href="#question-thomaskoppelaar" title="Answering Questions">💬</a> <a href="https://github.com/foambubble/foam/commits?author=thomaskoppelaar" title="Code">💻</a> <a href="#userTesting-thomaskoppelaar" title="User Testing">📓</a></td>
|
||||
<td align="center"><a href="http://www.akshaymehra.com"><img src="https://avatars1.githubusercontent.com/u/8671497?v=4?s=60" width="60px;" alt="Akshay"/><br /><sub><b>Akshay</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MehraAkshay" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://johnlindquist.com"><img src="https://avatars0.githubusercontent.com/u/36073?v=4?s=60" width="60px;" alt="John Lindquist"/><br /><sub><b>John Lindquist</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=johnlindquist" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ashwin.run/"><img src="https://avatars2.githubusercontent.com/u/1689183?v=4?s=60" width="60px;" alt="Ashwin Ramaswami"/><br /><sub><b>Ashwin Ramaswami</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=epicfaace" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Klaudioz"><img src="https://avatars1.githubusercontent.com/u/632625?v=4?s=60" width="60px;" alt="Claudio Canales"/><br /><sub><b>Claudio Canales</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Klaudioz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/vitaly-pevgonen"><img src="https://avatars0.githubusercontent.com/u/6272738?v=4?s=60" width="60px;" alt="vitaly-pevgonen"/><br /><sub><b>vitaly-pevgonen</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vitaly-pevgonen" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://dshemetov.github.io"><img src="https://avatars0.githubusercontent.com/u/1810426?v=4?s=60" width="60px;" alt="Dmitry Shemetov"/><br /><sub><b>Dmitry Shemetov</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dshemetov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hooncp"><img src="https://avatars1.githubusercontent.com/u/48883554?v=4?s=60" width="60px;" alt="hooncp"/><br /><sub><b>hooncp</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hooncp" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rt-canada.ca"><img src="https://avatars1.githubusercontent.com/u/13721239?v=4?s=60" width="60px;" alt="Martin Laws"/><br /><sub><b>Martin Laws</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=martinlaws" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://seanksmith.me"><img src="https://avatars3.githubusercontent.com/u/2085441?v=4?s=60" width="60px;" alt="Sean K Smith"/><br /><sub><b>Sean K Smith</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=sksmith" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/kevin-neely/"><img src="https://avatars1.githubusercontent.com/u/37545028?v=4?s=60" width="60px;" alt="Kevin Neely"/><br /><sub><b>Kevin Neely</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=kneely" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ariefrahmansyah.dev"><img src="https://avatars3.githubusercontent.com/u/8122852?v=4?s=60" width="60px;" alt="Arief Rahmansyah"/><br /><sub><b>Arief Rahmansyah</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ariefrahmansyah" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://vhanda.in"><img src="https://avatars2.githubusercontent.com/u/426467?v=4?s=60" width="60px;" alt="Vishesh Handa"/><br /><sub><b>Vishesh Handa</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=vHanda" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.linkedin.com/in/heroichitesh"><img src="https://avatars3.githubusercontent.com/u/37622734?v=4?s=60" width="60px;" alt="Hitesh Kumar"/><br /><sub><b>Hitesh Kumar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=HeroicHitesh" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://spencerwoo.com"><img src="https://avatars2.githubusercontent.com/u/32114380?v=4?s=60" width="60px;" alt="Spencer Woo"/><br /><sub><b>Spencer Woo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=spencerwooo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ingalless.com"><img src="https://avatars3.githubusercontent.com/u/22981941?v=4?s=60" width="60px;" alt="ingalless"/><br /><sub><b>ingalless</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=ingalless" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://jmg-duarte.github.io"><img src="https://avatars2.githubusercontent.com/u/15343819?v=4?s=60" width="60px;" alt="José Duarte"/><br /><sub><b>José Duarte</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Code">💻</a> <a href="https://github.com/foambubble/foam/commits?author=jmg-duarte" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.yenly.wtf"><img src="https://avatars1.githubusercontent.com/u/6759658?v=4?s=60" width="60px;" alt="Yenly"/><br /><sub><b>Yenly</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=yenly" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.hikerpig.cn"><img src="https://avatars1.githubusercontent.com/u/2259688?v=4?s=60" width="60px;" alt="hikerpig"/><br /><sub><b>hikerpig</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=hikerpig" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://sigfried.org"><img src="https://avatars1.githubusercontent.com/u/1586931?v=4?s=60" width="60px;" alt="Sigfried Gold"/><br /><sub><b>Sigfried Gold</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Sigfried" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.tristansokol.com"><img src="https://avatars3.githubusercontent.com/u/867661?v=4?s=60" width="60px;" alt="Tristan Sokol"/><br /><sub><b>Tristan Sokol</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=tristansokol" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://umbrellait.com"><img src="https://avatars0.githubusercontent.com/u/49779373?v=4?s=60" width="60px;" alt="Danil Rodin"/><br /><sub><b>Danil Rodin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=umbrellait-danil-rodin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/scottjoewilliams/"><img src="https://avatars1.githubusercontent.com/u/2026866?v=4?s=60" width="60px;" alt="Scott Williams"/><br /><sub><b>Scott Williams</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=scott-joe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jackiexiao.github.io/blog"><img src="https://avatars2.githubusercontent.com/u/18050469?v=4?s=60" width="60px;" alt="jackiexiao"/><br /><sub><b>jackiexiao</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Jackiexiao" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://generativist.substack.com/"><img src="https://avatars3.githubusercontent.com/u/78835?v=4?s=60" width="60px;" alt="John B Nelson"/><br /><sub><b>John B Nelson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jbn" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/asifm"><img src="https://avatars2.githubusercontent.com/u/3958387?v=4?s=60" width="60px;" alt="Asif Mehedi"/><br /><sub><b>Asif Mehedi</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=asifm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/litanlitudan"><img src="https://avatars2.githubusercontent.com/u/4970420?v=4?s=60" width="60px;" alt="Tan Li"/><br /><sub><b>Tan Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=litanlitudan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://shaunagordon.com"><img src="https://avatars1.githubusercontent.com/u/579361?v=4?s=60" width="60px;" alt="Shauna Gordon"/><br /><sub><b>Shauna Gordon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ShaunaGordon" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://mcluck.tech"><img src="https://avatars1.githubusercontent.com/u/1753801?v=4?s=60" width="60px;" alt="Mike Cluck"/><br /><sub><b>Mike Cluck</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MCluck90" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://brandonpugh.com"><img src="https://avatars1.githubusercontent.com/u/684781?v=4?s=60" width="60px;" alt="Brandon Pugh"/><br /><sub><b>Brandon Pugh</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bpugh" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://max.davitt.me"><img src="https://avatars1.githubusercontent.com/u/27709025?v=4?s=60" width="60px;" alt="Max Davitt"/><br /><sub><b>Max Davitt</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=themaxdavitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://briananglin.me"><img src="https://avatars3.githubusercontent.com/u/2637602?v=4?s=60" width="60px;" alt="Brian Anglin"/><br /><sub><b>Brian Anglin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=anglinb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://deft.work"><img src="https://avatars1.githubusercontent.com/u/1455507?v=4?s=60" width="60px;" alt="elswork"/><br /><sub><b>elswork</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elswork" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://leonh.fr/"><img src="https://avatars.githubusercontent.com/u/19996318?v=4?s=60" width="60px;" alt="léon h"/><br /><sub><b>léon h</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=leonhfr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nygaard.site"><img src="https://avatars.githubusercontent.com/u/4606342?v=4?s=60" width="60px;" alt="Nikhil Nygaard"/><br /><sub><b>Nikhil Nygaard</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=njnygaard" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nitwit.se"><img src="https://avatars.githubusercontent.com/u/1382124?v=4?s=60" width="60px;" alt="Mark Dixon"/><br /><sub><b>Mark Dixon</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=nitwit-se" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/joeltjames"><img src="https://avatars.githubusercontent.com/u/3732400?v=4?s=60" width="60px;" alt="Joel James"/><br /><sub><b>Joel James</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joeltjames" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.ryo33.com"><img src="https://avatars.githubusercontent.com/u/8780513?v=4?s=60" width="60px;" alt="Hashiguchi Ryo"/><br /><sub><b>Hashiguchi Ryo</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ryo33" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://movermeyer.com"><img src="https://avatars.githubusercontent.com/u/1459385?v=4?s=60" width="60px;" alt="Michael Overmeyer"/><br /><sub><b>Michael Overmeyer</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=movermeyer" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/derrickqin"><img src="https://avatars.githubusercontent.com/u/3038111?v=4?s=60" width="60px;" alt="Derrick Qin"/><br /><sub><b>Derrick Qin</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=derrickqin" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/zomars/"><img src="https://avatars.githubusercontent.com/u/3504472?v=4?s=60" width="60px;" alt="Omar López"/><br /><sub><b>Omar López</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=zomars" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://robincn.com"><img src="https://avatars.githubusercontent.com/u/1583193?v=4?s=60" width="60px;" alt="Robin King"/><br /><sub><b>Robin King</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=RobinKing" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/deegovee"><img src="https://avatars.githubusercontent.com/u/4730170?v=4?s=60" width="60px;" alt="Dheepak "/><br /><sub><b>Dheepak </b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dheepakg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/daniel-vera-g"><img src="https://avatars.githubusercontent.com/u/28257108?v=4?s=60" width="60px;" alt="Daniel VG"/><br /><sub><b>Daniel VG</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=daniel-vera-g" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Barabazs"><img src="https://avatars.githubusercontent.com/u/31799121?v=4?s=60" width="60px;" alt="Barabas"/><br /><sub><b>Barabas</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Barabazs" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://enginveske@gmail.com"><img src="https://avatars.githubusercontent.com/u/43685404?v=4?s=60" width="60px;" alt="Engincan VESKE"/><br /><sub><b>Engincan VESKE</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=EngincanV" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.paulderaaij.nl"><img src="https://avatars.githubusercontent.com/u/495374?v=4?s=60" width="60px;" alt="Paul de Raaij"/><br /><sub><b>Paul de Raaij</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=pderaaij" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bronson"><img src="https://avatars.githubusercontent.com/u/1776?v=4?s=60" width="60px;" alt="Scott Bronson"/><br /><sub><b>Scott Bronson</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bronson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://rafaelriedel.de"><img src="https://avatars.githubusercontent.com/u/41793?v=4?s=60" width="60px;" alt="Rafael Riedel"/><br /><sub><b>Rafael Riedel</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=rafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Pearcekieser"><img src="https://avatars.githubusercontent.com/u/5055971?v=4?s=60" width="60px;" alt="Pearcekieser"/><br /><sub><b>Pearcekieser</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Pearcekieser" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/theowenyoung"><img src="https://avatars.githubusercontent.com/u/62473795?v=4?s=60" width="60px;" alt="Owen Young"/><br /><sub><b>Owen Young</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=theowenyoung" title="Documentation">📖</a> <a href="#content-theowenyoung" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt="Prashanth Subrahmanyam"/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt="Jonas SPRENGER"/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt="Paul"/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt="memeplex"/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt="AndreiD049"/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iam-yan"><img src="https://avatars.githubusercontent.com/u/48427014?v=4?s=60" width="60px;" alt="Yan"/><br /><sub><b>Yan</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=iam-yan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://WikiEducator.org/User:JimTittsler"><img src="https://avatars.githubusercontent.com/u/180326?v=4?s=60" width="60px;" alt="Jim Tittsler"/><br /><sub><b>Jim Tittsler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=jimt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://malcolmmielle.wordpress.com/"><img src="https://avatars.githubusercontent.com/u/4457840?v=4?s=60" width="60px;" alt="Malcolm Mielle"/><br /><sub><b>Malcolm Mielle</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=MalcolmMielle" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://snippets.page/"><img src="https://avatars.githubusercontent.com/u/74916913?v=4?s=60" width="60px;" alt="Veesar"/><br /><sub><b>Veesar</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=veesar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bentongxyz"><img src="https://avatars.githubusercontent.com/u/60358804?v=4?s=60" width="60px;" alt="bentongxyz"/><br /><sub><b>bentongxyz</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=bentongxyz" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://brianjdevries.com"><img src="https://avatars.githubusercontent.com/u/42778030?v=4?s=60" width="60px;" alt="Brian DeVries"/><br /><sub><b>Brian DeVries</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=techCarpenter" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://Cliffordfajardo.com"><img src="https://avatars.githubusercontent.com/u/6743796?v=4?s=60" width="60px;" alt="Clifford Fajardo "/><br /><sub><b>Clifford Fajardo </b></sub></a><br /><a href="#tool-cliffordfajardo" title="Tools">🔧</a></td>
|
||||
<td align="center"><a href="http://cu-dev.ca"><img src="https://avatars.githubusercontent.com/u/6589365?v=4?s=60" width="60px;" alt="Chris Usick"/><br /><sub><b>Chris Usick</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=chrisUsick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/josephdecock"><img src="https://avatars.githubusercontent.com/u/1145533?v=4?s=60" width="60px;" alt="Joe DeCock"/><br /><sub><b>Joe DeCock</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=josephdecock" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.drewtyler.com"><img src="https://avatars.githubusercontent.com/u/5640816?v=4?s=60" width="60px;" alt="Drew Tyler"/><br /><sub><b>Drew Tyler</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=drewtyler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Lauviah0622"><img src="https://avatars.githubusercontent.com/u/43416399?v=4?s=60" width="60px;" alt="Lauviah0622"/><br /><sub><b>Lauviah0622</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Lauviah0622" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.elastic.co/elastic-agent"><img src="https://avatars.githubusercontent.com/u/1813008?v=4?s=60" width="60px;" alt="Josh Dover"/><br /><sub><b>Josh Dover</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=joshdover" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://phelm.co.uk"><img src="https://avatars.githubusercontent.com/u/4057948?v=4?s=60" width="60px;" alt="Phil Helm"/><br /><sub><b>Phil Helm</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=phelma" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/lingyv-li"><img src="https://avatars.githubusercontent.com/u/8937944?v=4?s=60" width="60px;" alt="Larry Li"/><br /><sub><b>Larry Li</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=lingyv-li" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/infogulch"><img src="https://avatars.githubusercontent.com/u/133882?v=4?s=60" width="60px;" alt="Joe Taber"/><br /><sub><b>Joe Taber</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=infogulch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.readingsnail.pe.kr"><img src="https://avatars.githubusercontent.com/u/1904967?v=4?s=60" width="60px;" alt="Woosuk Park"/><br /><sub><b>Woosuk Park</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=readingsnail" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://www.dmurph.com"><img src="https://avatars.githubusercontent.com/u/294026?v=4?s=60" width="60px;" alt="Daniel Murphy"/><br /><sub><b>Daniel Murphy</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=dmurph" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dominic-DallOsto"><img src="https://avatars.githubusercontent.com/u/26859884?v=4?s=60" width="60px;" alt="Dominic D"/><br /><sub><b>Dominic D</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Dominic-DallOsto" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://elgirafo.xyz"><img src="https://avatars.githubusercontent.com/u/80516439?v=4?s=60" width="60px;" alt="luca"/><br /><sub><b>luca</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=elgirafo" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
27
yarn.lock
27
yarn.lock
@@ -2222,9 +2222,9 @@
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/braces@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb"
|
||||
integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.1.tgz#5a284d193cfc61abb2e5a50d36ebbc50d942a32b"
|
||||
integrity sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==
|
||||
|
||||
"@types/dateformat@^3.0.1":
|
||||
version "3.0.1"
|
||||
@@ -2340,9 +2340,9 @@
|
||||
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
|
||||
|
||||
"@types/micromatch@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7"
|
||||
integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.2.tgz#ce29c8b166a73bf980a5727b1e4a4d099965151d"
|
||||
integrity sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==
|
||||
dependencies:
|
||||
"@types/braces" "*"
|
||||
|
||||
@@ -5087,11 +5087,6 @@ extsprintf@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fast-array-diff@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-array-diff/-/fast-array-diff-1.0.1.tgz#463bacfeddaa3f5d56b79f6847fe322f15581c92"
|
||||
integrity sha512-pU83E/Y7+c/hRrDlmIiPYHy0Ugt+QypqzHKZI5qFOWMJAspWdmOyIeN/1FbdnGPlROp6FeGLLfhMO075DBqb4A==
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
@@ -9548,11 +9543,6 @@ remark-wiki-link@^0.0.4:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
unist-util-map "^1.0.3"
|
||||
|
||||
remove-markdown@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-markdown/-/remove-markdown-0.3.0.tgz#5e4b667493a93579728f3d52ecc1db9ca505dc98"
|
||||
integrity sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
@@ -9575,11 +9565,6 @@ repeating@^2.0.0:
|
||||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
replace-ext@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06"
|
||||
integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==
|
||||
|
||||
request-promise-core@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
|
||||
|
||||
Reference in New Issue
Block a user