Push draft of 2.0.0rc
parent
b5b4f96d6c
commit
fdfcc9c5b3
@ -0,0 +1,372 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/visualstudio
|
||||
|
||||
### VisualStudio ###
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.vcxproj.filters
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/
|
||||
|
||||
doc/
|
||||
@ -0,0 +1,104 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${file}",
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Integrated Terminal/Console",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "External Terminal/Console",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${file}",
|
||||
"console": "externalTerminal",
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Django",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${workspaceRoot}/manage.py",
|
||||
"args": [
|
||||
"runserver",
|
||||
"--noreload"
|
||||
],
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput",
|
||||
"DjangoDebugging"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${workspaceRoot}/run.py",
|
||||
"args": [],
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Watson",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": true,
|
||||
"pythonPath": "${config.python.pythonPath}",
|
||||
"program": "${workspaceRoot}/console.py",
|
||||
"args": [
|
||||
"dev",
|
||||
"runserver",
|
||||
"--noreload=True"
|
||||
],
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Attach (Remote Debug)",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"localRoot": "${workspaceRoot}",
|
||||
"remoteRoot": "${workspaceRoot}",
|
||||
"port": 3000,
|
||||
"secret": "my_secret",
|
||||
"host": "localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.linting.pylintEnabled": false
|
||||
}
|
||||
@ -1,781 +0,0 @@
|
||||
# Ambar Web API v1.2.0
|
||||
|
||||
Ambar Web API documentation
|
||||
|
||||
- [Files](#files)
|
||||
- [Get File Meta by File Id](#get-file-meta-by-file-id)
|
||||
- [Get File Source by File Id](#get-file-source-by-file-id)
|
||||
- [Get Parsed Text From File by File Id](#get-parsed-text-from-file-by-file-id)
|
||||
- [Download File Content by Secure Uri](#download-file-content-by-secure-uri)
|
||||
- [Download Parsed Text by Secure Uri](#download-parsed-text-by-secure-uri)
|
||||
- [Upload File](#upload-file)
|
||||
- [Hide File](#hide-file)
|
||||
- [Unhide File](#unhide-file)
|
||||
|
||||
- [Search](#search)
|
||||
- [Search For Documents By Query](#search-for-documents-by-query)
|
||||
- [Retrieve File Highlight by Query and fileId](#retrieve-file-highlight-by-query-and-fileid)
|
||||
- [Retrieve Full File Highlight by Query and fileId](#retrieve-full-file-highlight-by-query-and-fileid)
|
||||
|
||||
- [Sources](#sources)
|
||||
- [Get Available Sources](#get-available-sources)
|
||||
|
||||
- [Statistics](#statistics)
|
||||
- [Get Statistics](#get-statistics)
|
||||
|
||||
- [Tags](#tags)
|
||||
- [Delete Tag From File](#delete-tag-from-file)
|
||||
- [Get Tags](#get-tags)
|
||||
- [Add Tag For File](#add-tag-for-file)
|
||||
|
||||
- [Thumbnails](#thumbnails)
|
||||
- [Get Thumbnail by Id](#get-thumbnail-by-id)
|
||||
- [Add or Update Thumbnail](#add-or-update-thumbnail)
|
||||
|
||||
- [Users](#users)
|
||||
- [Login](#login)
|
||||
- [Logout](#logout)
|
||||
|
||||
|
||||
|
||||
# Files
|
||||
|
||||
## Get File Meta by File Id
|
||||
|
||||
|
||||
|
||||
GET api/files/direct/:fileId/meta
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Get File Source by File Id
|
||||
|
||||
|
||||
|
||||
GET api/files/direct/:fileId/source
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Get Parsed Text From File by File Id
|
||||
|
||||
|
||||
|
||||
GET api/files/direct/:fileId/text
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Download File Content by Secure Uri
|
||||
|
||||
|
||||
|
||||
GET api/files/:uri
|
||||
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Download Parsed Text by Secure Uri
|
||||
|
||||
|
||||
|
||||
GET api/files/:uri/text
|
||||
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Upload File
|
||||
|
||||
<p>New source named <code>uiupload</code> with description <code>Automatically created on UI upload</code> will be created if source didn't exist.</p>
|
||||
|
||||
POST api/files/uiupload/:filename
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Examples
|
||||
|
||||
Upload File test.txt
|
||||
|
||||
```
|
||||
curl -X POST \
|
||||
http://ambar_api_address/api/files/uiupload/test.txt \
|
||||
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
|
||||
-F file=@test.txt
|
||||
```
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{ "fileId": xxxxx }
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
|
||||
```
|
||||
Wrong request data
|
||||
```
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
```
|
||||
File meta or content not found
|
||||
```
|
||||
## Hide File
|
||||
|
||||
<p>Hide file by file id</p>
|
||||
|
||||
PUT api/files/hide/:fileId
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 NotFound
|
||||
|
||||
```
|
||||
File not found
|
||||
```
|
||||
## Unhide File
|
||||
|
||||
<p>Unhide file by file id</p>
|
||||
|
||||
PUT api/files/unhide/:fileId
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 NotFound
|
||||
|
||||
```
|
||||
File not found
|
||||
```
|
||||
# Search
|
||||
|
||||
## Search For Documents By Query
|
||||
|
||||
|
||||
|
||||
GET api/search
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email.</p> |
|
||||
| ambar-email-token | String | <p>User token.</p> |
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| query | String | <p>URI_ENCODED query string. Check details of query syntax <a href="https://blog.ambar.cloud/mastering-ambar-search-queries/">here</a>.</p> |
|
||||
| page | Number | **optional** <p>page to return</p> |
|
||||
| size | Number | **optional** <p>number of results to return per page. Maximum is 100.</p> |
|
||||
|
||||
### Examples
|
||||
|
||||
Search For `John`
|
||||
|
||||
```
|
||||
curl -i http://ambar_api_address/api/search?query=John
|
||||
```
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
|
||||
{
|
||||
"total":1,
|
||||
"hits":[
|
||||
{
|
||||
"sha256":"60a777c59176e98efee98bf16b67983dc981ec4da3eaafcb4d79046d005456f9",
|
||||
"meta":{
|
||||
"id":"ac8965ab5e07582e0e57cde0e7c4c2d49b955f8b26c779903191893fcb942fa4",
|
||||
"full_name":"//mail.nic.ru/hello@ambar.cloud/linus torvalds talk of tech innovation is bullshit shut up and get the work done fcc chairman wants it to be easier to listen to free fm radio on your smartphone.eml",
|
||||
"short_name":"linus torvalds talk of tech innovation is bullshit shut up and get the work done fcc chairman wants it to be easier to listen to free fm radio on your smartphone.eml",
|
||||
"extension":".eml",
|
||||
"extra":[
|
||||
],
|
||||
"source_id":"AmbarEmail",
|
||||
"created_datetime":"2017-02-17 09:22:44.000",
|
||||
"updated_datetime":"2017-02-17 09:22:44.000",
|
||||
"download_uri":"b41c4aaa2999ce42957f087db8e7608970efcedb1eaa40c28336390ecb5373849c955f395258f3dfd7482d4b84d543cdfc23cff8df311276a5e111c0504315c60b159cd2fe2cee20c5470789d9d15e4d7e5fb7c2bc60c29bf9a578e47541fb354dcb5109e49ea9019b2d68c3b35e521a418d9c94f0af55dc79c2442188f039c924d0190c72f488ad77647f2a52aaa267"
|
||||
},
|
||||
"indexed_datetime":"2017-05-31 13:36:40.400",
|
||||
"file_id":"aa5e000fd79cfed0e839af7073e1ef135e128408f984b9a8e70e34242b49f01a",
|
||||
"content":{
|
||||
"size":49282,
|
||||
"author":"Slashdot Headlines <slashdot@newsletters.slashdot.org>",
|
||||
"ocr_performed":false,
|
||||
"processed_datetime":"2017-05-31 13:36:40.361",
|
||||
"length":"",
|
||||
"language":"",
|
||||
"thumb_available":false,
|
||||
"state":"processed",
|
||||
"title":"",
|
||||
"type":"message/rfc822",
|
||||
"highlight":{
|
||||
"text":[
|
||||
"__________________________________________________________________________<br/>Linus Torvalds: Talk of Tech Innovation is Bullshit. Shut Up and Get the Work Done<br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,d9zf,fh0y,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em><br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,a5s3,9k63,9dml,a0z3<br/>FCC Chairman Wants It To Be Easier To Listen To Free FM Radio On Your Smartphone<br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp",
|
||||
"self-serving. From a report on The Register: The term of art he used was more blunt: \"The innovation the industry talks about so much is... Read More http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,aiki,d8f2,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em> http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,ezm,35uk,9dml,a0z3<br/>From the boring-company department<br/>Sometimes it is hard to tell if Elon Musk is serious about the things he says. But as for his \"boring\" claims, that's",
|
||||
"email to: unsubscribe-47676@elabs10.com<br/>Slashdot | 1660 Logan Ave. Ste A | San Diego, CA 92113<br/>To view our Privacy Policy: http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,8pii,7uiv,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em> | Lost Winston Churchill Essay Reveals His Thoughts On Alien<br/>Life<br/>All the Power of a Windows 10 PC Right In Your Pocket<br/>As the world gets more advanced, technology is getting",
|
||||
"WiFi and Bluetooth. Plus, with<br/>a wide range of inputs and outputs, you can link with just about any device you want. Learn More!<br/>Linus Torvalds: Talk of Tech Innovation is Bullshit. Shut Up and Get the Work Done <br/><em>Elon Musk</em> Is <em>Really Boring</em> <br/>FCC Chairman Wants It To Be Easier To Listen To Free FM Radio On Your Smartphone <br/>Lost Winston Churchill Essay Reveals His Thoughts On Alien Life <br/>JavaScript Attack Breaks ASLR On 22 CPU Architectures <br/>Ethicists",
|
||||
"of innovation is smug, self-congratulatory, and self-serving. From a report on The Register: The term of art he used was more blunt: \"The innovation the industry talks about so much is...<br/><em>Elon Musk</em> Is <em>Really Boring</em> <br/>From the boring-company department<br/>Sometimes it is hard to tell if Elon Musk is serious about the things he says. But as for his \"boring\" claims, that's really happening. In a wide-range interview with Bloomberg"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tags":[
|
||||
],
|
||||
"score":1
|
||||
}
|
||||
],
|
||||
"took":24.672135
|
||||
}
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 BadRequest
|
||||
|
||||
```
|
||||
HTTP/1.1 400 BadRequest
|
||||
```
|
||||
## Retrieve File Highlight by Query and fileId
|
||||
|
||||
<p>This method is useful for getting higlights of large files > 30 MB</p>
|
||||
|
||||
GET api/search/:fileId
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email.</p> |
|
||||
| ambar-email-token | String | <p>User token.</p> |
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| fileId | String | <p>file fileId</p> |
|
||||
| query | String | <p>query string</p> |
|
||||
|
||||
### Examples
|
||||
|
||||
Retrieve Higlights for File with fileId `318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3`
|
||||
|
||||
```
|
||||
curl -i http://ambar:8004/api/search/318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3?query=John
|
||||
```
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{
|
||||
"highlight": {
|
||||
"text": [
|
||||
"Aesop, by some strange accident it seems to have entirely<br/>disappeared, and to have been lost sight of. His name is<br/>mentioned by Avienus; by Suidas, a celebrated critic, at the<br/>close of the eleventh century, who gives in his lexicon several<br/>isolated verses of his version of the fables; and by <em>John</em><br/>Tzetzes, a grammarian and poet of Constantinople, who lived<br/>during the latter half of the twelfth century. Nevelet, in the<br/>preface to the volume which we have described, points out that<br/>the Fables of Planudes could not be the work of Aesop, as they<br/>contain a reference in two places to \"Holy"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 BadRequest
|
||||
|
||||
```
|
||||
HTTP/1.1 400 BadRequest
|
||||
```
|
||||
## Retrieve Full File Highlight by Query and fileId
|
||||
|
||||
<p>This method is useful for getting higlights of large files > 30 MB</p>
|
||||
|
||||
GET api/search/:fileId/full
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email.</p> |
|
||||
| ambar-email-token | String | <p>User token.</p> |
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| fileId | String | <p>file fileId</p> |
|
||||
| query | String | <p>query string</p> |
|
||||
|
||||
### Examples
|
||||
|
||||
Retrieve Full Higlight for File with fileId `318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3`
|
||||
|
||||
```
|
||||
curl -i http://ambar:8004/api/search/318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3/full?query=John
|
||||
```
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Aesop, by some strange accident it seems to have entirely<br/>disappeared, and to have been lost sight of. His name is<br/>mentioned by Avienus; by Suidas, a celebrated critic, at the<br/>close of the eleventh century, who gives in his lexicon several<br/>isolated verses of his version of the fables; and by <em>John</em><br/>Tzetzes, a grammarian and poet of Constantinople, who lived<br/>during the latter half of the twelfth century. Nevelet, in the<br/>preface to the volume which we have described, points out that<br/>the Fables of Planudes could not be the work of Aesop, as they<br/>contain a reference in two places to Holy
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 BadRequest
|
||||
|
||||
```
|
||||
HTTP/1.1 400 BadRequest
|
||||
```
|
||||
# Sources
|
||||
|
||||
## Get Available Sources
|
||||
|
||||
<p>Get Available Sources (Crawlers Included)</p>
|
||||
|
||||
GET api/sources/
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email.</p> |
|
||||
| ambar-email-token | String | <p>User token.</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"id": "Default",
|
||||
"description": "Automatically created on UI upload",
|
||||
"type": "bucket"
|
||||
},
|
||||
{
|
||||
"id": "Books",
|
||||
"description": "Books crawler",
|
||||
"type": "crawler"
|
||||
},
|
||||
{
|
||||
"id": "Dropbox",
|
||||
"description": "Dropbox Crawler",
|
||||
"type": "crawler"
|
||||
}
|
||||
]
|
||||
```
|
||||
# Statistics
|
||||
|
||||
## Get Statistics
|
||||
|
||||
|
||||
|
||||
GET api/stats
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{
|
||||
"contentType": {
|
||||
"total": 2,
|
||||
"minThreshold": 0.1,
|
||||
"data": [
|
||||
{
|
||||
"name": "application/msword",
|
||||
"value": 1,
|
||||
"sizeDataInBytes": {
|
||||
"count": 1,
|
||||
"min": 91681,
|
||||
"max": 91681,
|
||||
"avg": 91681,
|
||||
"sum": 91681
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"procRate": {
|
||||
"data": [
|
||||
{
|
||||
"date": "2017-04-13",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-04-14",
|
||||
"default": 2
|
||||
}
|
||||
],
|
||||
"names": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
"procTotal": {
|
||||
"totalCount": 2,
|
||||
"sizeDataInBytes": {
|
||||
"sum": 147522,
|
||||
"avg": 73761,
|
||||
"min": 55841,
|
||||
"max": 91681
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
# Tags
|
||||
|
||||
## Delete Tag From File
|
||||
|
||||
|
||||
|
||||
DELETE api/tags/:fileId/:tagType/:tagName
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| fileId | String | <p>File Id to delete tag from.</p> |
|
||||
| tagType | String | <p>Tag type to delete.</p> |
|
||||
| tagName | String | <p>Tag name to delete.</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{
|
||||
"tags":[
|
||||
{
|
||||
"name":"ocr",
|
||||
"filesCount":3
|
||||
},
|
||||
{
|
||||
"name":"test",
|
||||
"filesCount":2
|
||||
},
|
||||
{
|
||||
"name":"pdf",
|
||||
"filesCount":1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
## Get Tags
|
||||
|
||||
|
||||
|
||||
GET api/tags/
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name":"ocr",
|
||||
"filesCount":3
|
||||
},
|
||||
{
|
||||
"name":"test",
|
||||
"filesCount":2
|
||||
},
|
||||
{
|
||||
"name":"pdf",
|
||||
"filesCount":1
|
||||
}
|
||||
]
|
||||
```
|
||||
## Add Tag For File
|
||||
|
||||
|
||||
|
||||
POST api/tags/:fileId/:tagType/:tagName
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| fileId | String | <p>File Id to add tag to.</p> |
|
||||
| tagType | String | <p>Tag type to add.</p> |
|
||||
| tagName | String | <p>Tag name to add.</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{
|
||||
"tagId":"e9536a83e64ff03617ab0379d835ac7bbf213bafb95cb42907a56e735472d4fc",
|
||||
"tags":[
|
||||
{
|
||||
"name":"ocr",
|
||||
"filesCount":3
|
||||
},
|
||||
{
|
||||
"name":"test",
|
||||
"filesCount":2
|
||||
},
|
||||
{
|
||||
"name":"pdf",
|
||||
"filesCount":1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
# Thumbnails
|
||||
|
||||
## Get Thumbnail by Id
|
||||
|
||||
|
||||
|
||||
GET api/thumbs/:id
|
||||
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
Octet-Stream
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 404 NotFound
|
||||
|
||||
```
|
||||
HTTP/1.1 404 NotFound
|
||||
```
|
||||
## Add or Update Thumbnail
|
||||
|
||||
|
||||
|
||||
POST api/thumbs/:id
|
||||
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
|
||||
```
|
||||
Request body is empty
|
||||
```
|
||||
# Users
|
||||
|
||||
## Login
|
||||
|
||||
|
||||
|
||||
POST api/users/login
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| email | String | <p>User Email</p> |
|
||||
| password | String | <p>User Password</p> |
|
||||
|
||||
### Success Response
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
```
|
||||
{
|
||||
"token": "504d44935c2ccefb557fd49636a73239147b3895db2f2f...",
|
||||
"ttl": "604800"
|
||||
}
|
||||
```
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 400 BadRequest
|
||||
|
||||
```
|
||||
Bad request
|
||||
```
|
||||
HTTP/1.1 404 NotFound
|
||||
|
||||
```
|
||||
User with specified email not found
|
||||
```
|
||||
HTTP/1.1 409 Conflict
|
||||
|
||||
```
|
||||
User is not in active state
|
||||
```
|
||||
HTTP/1.1 401 Unauthorized
|
||||
|
||||
```
|
||||
Wrong password
|
||||
```
|
||||
## Logout
|
||||
|
||||
|
||||
|
||||
POST api/users/logout
|
||||
|
||||
### Headers
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------|-----------|--------------------------------------|
|
||||
| ambar-email | String | <p>User email</p> |
|
||||
| ambar-email-token | String | <p>User token</p> |
|
||||
|
||||
### Error Response
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
|
||||
```
|
||||
Unauthorized
|
||||
```
|
||||
|
||||
@ -0,0 +1,371 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/visualstudio
|
||||
|
||||
### VisualStudio ###
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.vcxproj.filters
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
FROM elasticsearch:5.6.3
|
||||
|
||||
# Set a timezone
|
||||
ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
COPY elasticsearch.yml ./config/elasticsearch.yml
|
||||
|
||||
RUN bin/elasticsearch-plugin install http://dl.bintray.com/content/imotov/elasticsearch-plugins/org/elasticsearch/elasticsearch-analysis-morphology/5.6.3/elasticsearch-analysis-morphology-5.6.3.zip
|
||||
RUN bin/elasticsearch-plugin install analysis-stempel
|
||||
RUN bin/elasticsearch-plugin install analysis-smartcn
|
||||
|
||||
CMD ["elasticsearch"]
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=30s --retries=50 \
|
||||
CMD curl -f http://localhost:9200/ || exit 1
|
||||
@ -0,0 +1,12 @@
|
||||
network.host: 0.0.0.0
|
||||
|
||||
# this value is required because we set "network.host"
|
||||
# be sure to modify it appropriately for a production cluster deployment
|
||||
discovery.zen.minimum_master_nodes: 1
|
||||
|
||||
http.max_content_length: 1024mb
|
||||
http.cors.enabled: true
|
||||
http.cors.allow-origin: "*"
|
||||
node.ingest: false
|
||||
action.auto_create_index: false
|
||||
bootstrap.memory_lock: true
|
||||
@ -0,0 +1,10 @@
|
||||
// NOTE: These options are overriden by the babel-loader configuration
|
||||
// for webpack, which can be found in ~/build/webpack.config.
|
||||
//
|
||||
// Why? The react-transform-hmr plugin depends on HMR (and throws if
|
||||
// module.hot is disabled), so keeping it and related plugins contained
|
||||
// within webpack helps prevent unexpected errors.
|
||||
{
|
||||
"presets": ["es2015", "react", "stage-0"],
|
||||
"plugins": ["transform-runtime"]
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
# A special property that should be specified at the top of the file outside of
|
||||
# any sections. Set to true to stop .editor config file search on current file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# Indentation style
|
||||
# Possible values - tab, space
|
||||
indent_style = space
|
||||
|
||||
# Indentation size in single-spaced characters
|
||||
# Possible values - an integer, tab
|
||||
indent_size = 2
|
||||
|
||||
# Line ending file format
|
||||
# Possible values - lf, crlf, cr
|
||||
end_of_line = lf
|
||||
|
||||
# File character encoding
|
||||
# Possible values - latin1, utf-8, utf-16be, utf-16le
|
||||
charset = utf-8
|
||||
|
||||
# Denotes whether to trim whitespace at the end of lines
|
||||
# Possible values - true, false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Denotes whether file should end with a newline
|
||||
# Possible values - true, false
|
||||
insert_final_newline = true
|
||||
@ -0,0 +1,6 @@
|
||||
blueprints/**/files/**
|
||||
coverage/**
|
||||
node_modules/**
|
||||
dist/**
|
||||
*.spec.js
|
||||
src/index.html
|
||||
@ -0,0 +1,29 @@
|
||||
{
|
||||
"parser" : "babel-eslint",
|
||||
"extends" : [
|
||||
"standard",
|
||||
"standard-react"
|
||||
],
|
||||
"plugins": [
|
||||
"babel",
|
||||
"react",
|
||||
"promise"
|
||||
],
|
||||
"env" : {
|
||||
"browser" : true
|
||||
},
|
||||
"globals" : {
|
||||
"__DEV__" : false,
|
||||
"__PROD__" : false,
|
||||
"__DEBUG__" : false,
|
||||
"__COVERAGE__" : false,
|
||||
"__BASENAME__" : false
|
||||
},
|
||||
"rules": {
|
||||
"semi" : [2, "never"],
|
||||
"max-len": [2, 120, 2],
|
||||
"generator-star-spacing": 0,
|
||||
"babel/generator-star-spacing": 1,
|
||||
"jsx-quotes": [2, "prefer-single"]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,371 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/python
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/visualstudio
|
||||
|
||||
### VisualStudio ###
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.vcxproj.filters
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sourceBase":"src",
|
||||
"testBase":"tests",
|
||||
"smartPath":"containers",
|
||||
"dumbPath":"components",
|
||||
"fileCasing":"pascal"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
FROM nginx:latest
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y curl
|
||||
|
||||
# Set a timezone
|
||||
ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
COPY default /etc/nginx/conf.d/default.conf
|
||||
COPY dist /usr/share/nginx/html
|
||||
|
||||
CMD echo $api > /usr/share/nginx/html/apiUrl.txt && nginx -g "daemon off;"
|
||||
|
||||
HEALTHCHECK --interval=5s --timeout=30s --retries=50 \
|
||||
CMD curl -f localhost:80 || exit 1
|
||||
@ -0,0 +1,24 @@
|
||||
import fs from 'fs-extra'
|
||||
import _debug from 'debug'
|
||||
import webpackCompiler from '../build/webpack-compiler'
|
||||
import webpackConfig from '../build/webpack.config'
|
||||
import config from '../config'
|
||||
|
||||
const debug = _debug('app:bin:compile')
|
||||
const paths = config.utils_paths
|
||||
|
||||
;(async function () {
|
||||
try {
|
||||
debug('Run compiler')
|
||||
const stats = await webpackCompiler(webpackConfig)
|
||||
if (stats.warnings.length && config.compiler_fail_on_warning) {
|
||||
debug('Config set to fail on warning, exiting with status code "1".')
|
||||
process.exit(1)
|
||||
}
|
||||
debug('Copy static assets to dist folder.')
|
||||
fs.copySync(paths.client('static'), paths.dist())
|
||||
} catch (e) {
|
||||
debug('Compiler encountered an error.', e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
@ -0,0 +1,11 @@
|
||||
import config from '../config'
|
||||
import server from '../server/main'
|
||||
import _debug from 'debug'
|
||||
|
||||
const debug = _debug('app:bin:server')
|
||||
const port = config.server_port
|
||||
const host = config.server_host
|
||||
|
||||
server.listen(port)
|
||||
debug(`Server is now running at http://${host}:${port}.`)
|
||||
debug(`Server accessible via localhost:${port} if you are using the project defaults.`)
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends" : "../.eslintrc",
|
||||
"env" : {
|
||||
"mocha" : true
|
||||
},
|
||||
"globals" : {
|
||||
"expect" : false,
|
||||
"should" : false,
|
||||
"sinon" : false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import classes from './<%= pascalEntityName %>.scss'
|
||||
|
||||
export const <%= pascalEntityName %> = () => (
|
||||
<div className={classes['<%= pascalEntityName %>']}>
|
||||
<h1><%= pascalEntityName %></h1>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default <%= pascalEntityName %>
|
||||
@ -0,0 +1,2 @@
|
||||
.<%= pascalEntityName %> {
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import <%= pascalEntityName %> from './<%= pascalEntityName %>'
|
||||
|
||||
export default <%= pascalEntityName %>
|
||||
@ -0,0 +1,25 @@
|
||||
// module.exports = {
|
||||
// locals: function(options) {
|
||||
// // Return custom template variables here.
|
||||
// return {};
|
||||
// },
|
||||
|
||||
// fileMapTokens: function(options) (
|
||||
// // Return custom tokens to be replaced in your files
|
||||
// return {
|
||||
// __token__: function(options){
|
||||
// // logic to determine value goes here
|
||||
// return 'value';
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
|
||||
// Should probably never need to be overriden
|
||||
//
|
||||
// filesPath: function() {
|
||||
// return path.join(this.path, 'files');
|
||||
// },
|
||||
|
||||
// beforeInstall: function(options) {},
|
||||
// afterInstall: function(options) {},
|
||||
// };
|
||||
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import classes from './<%= pascalEntityName %>.scss'
|
||||
|
||||
export const <%= pascalEntityName %> = () => (
|
||||
<div className={classes['<%= pascalEntityName %>']}>
|
||||
<h4><%= pascalEntityName %></h4>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default <%= pascalEntityName %>
|
||||
@ -0,0 +1,3 @@
|
||||
.<%= pascalEntityName %> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { increment, doubleAsync } from '../modules/<%= pascalEntityName %>'
|
||||
|
||||
/* This is a container component. Notice it does not contain any JSX,
|
||||
nor does it import React. This component is **only** responsible for
|
||||
wiring in the actions and state necessary to render a presentational
|
||||
component - in this case, the counter: */
|
||||
|
||||
import <%= pascalEntityName %> from '../components/<%= pascalEntityName %>'
|
||||
|
||||
/* Object of action creators (can also be function that returns object).
|
||||
Keys will be passed as props to presentational components. Here we are
|
||||
implementing our wrapper around increment; the component doesn't care */
|
||||
|
||||
const mapActionCreators = {
|
||||
increment: () => increment(1),
|
||||
doubleAsync
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
counter: state.counter
|
||||
})
|
||||
|
||||
/* Note: mapStateToProps is where you should use `reselect` to create selectors, ie:
|
||||
|
||||
import { createSelector } from 'reselect'
|
||||
const counter = (state) => state.counter
|
||||
const tripleCount = createSelector(counter, (count) => count * 3)
|
||||
const mapStateToProps = (state) => ({
|
||||
counter: tripleCount(state)
|
||||
})
|
||||
|
||||
Selectors can compute derived data, allowing Redux to store the minimal possible state.
|
||||
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
|
||||
Selectors are composable. They can be used as input to other selectors.
|
||||
https://github.com/reactjs/reselect */
|
||||
|
||||
export default connect(mapStateToProps, mapActionCreators)(<%= pascalEntityName %>)
|
||||
@ -0,0 +1,24 @@
|
||||
import { injectReducer } from '../../store/reducers'
|
||||
|
||||
export default (store) => ({
|
||||
path: '<%= dashesEntityName %>',
|
||||
/* Async getComponent is only invoked when route matches */
|
||||
getComponent (nextState, cb) {
|
||||
/* Webpack - use 'require.ensure' to create a split point
|
||||
and embed an async module loader (jsonp) when bundling */
|
||||
require.ensure([], (require) => {
|
||||
/* Webpack - use require callback to define
|
||||
dependencies for bundling */
|
||||
const <%= pascalEntityName %> = require('./containers/<%= pascalEntityName %>Container').default
|
||||
const reducer = require('./modules/<%= pascalEntityName %>').default
|
||||
|
||||
/* Add the reducer to the store on key 'counter' */
|
||||
injectReducer(store, { key: '<%= pascalEntityName %>', reducer })
|
||||
|
||||
/* Return getComponent */
|
||||
cb(null, <%= pascalEntityName %>)
|
||||
|
||||
/* Webpack named bundle */
|
||||
}, '<%= pascalEntityName %>')
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,55 @@
|
||||
// ------------------------------------
|
||||
// Constants
|
||||
// ------------------------------------
|
||||
export const COUNTER_INCREMENT = '<%= pascalEntityName %>.COUNTER_INCREMENT'
|
||||
|
||||
// ------------------------------------
|
||||
// Actions
|
||||
// ------------------------------------
|
||||
export function increment (value = 1) {
|
||||
return {
|
||||
type: COUNTER_INCREMENT,
|
||||
payload: value
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a thunk, meaning it is a function that immediately
|
||||
returns a function for lazy evaluation. It is incredibly useful for
|
||||
creating async actions, especially when combined with redux-thunk!
|
||||
|
||||
NOTE: This is solely for demonstration purposes. In a real application,
|
||||
you'd probably want to dispatch an action of COUNTER_DOUBLE and let the
|
||||
reducer take care of this logic. */
|
||||
|
||||
export const doubleAsync = () => {
|
||||
return (dispatch, getState) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
dispatch(increment(getState().counter))
|
||||
resolve()
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
increment,
|
||||
doubleAsync
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Action Handlers
|
||||
// ------------------------------------
|
||||
const ACTION_HANDLERS = {
|
||||
[COUNTER_INCREMENT]: (state, action) => state + action.payload
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Reducer
|
||||
// ------------------------------------
|
||||
const initialState = 0
|
||||
export default function counterReducer (state = initialState, action) {
|
||||
const handler = ACTION_HANDLERS[action.type]
|
||||
|
||||
return handler ? handler(state, action) : state
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
// module.exports = {
|
||||
// locals: function(options) {
|
||||
// // Return custom template variables here.
|
||||
// return {};
|
||||
// },
|
||||
|
||||
// fileMapTokens: function(options) (
|
||||
// // Return custom tokens to be replaced in your files
|
||||
// return {
|
||||
// __token__: function(options){
|
||||
// // logic to determine value goes here
|
||||
// return 'value';
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
|
||||
// Should probably never need to be overriden
|
||||
//
|
||||
// filesPath: function() {
|
||||
// return path.join(this.path, 'files');
|
||||
// },
|
||||
|
||||
// beforeInstall: function(options) {},
|
||||
// afterInstall: function(options) {},
|
||||
// };
|
||||
@ -0,0 +1,76 @@
|
||||
import { argv } from 'yargs'
|
||||
import config from '../config'
|
||||
import webpackConfig from './webpack.config'
|
||||
import _debug from 'debug'
|
||||
|
||||
const debug = _debug('app:karma')
|
||||
debug('Create configuration.')
|
||||
|
||||
const karmaConfig = {
|
||||
basePath: '../', // project root in relation to bin/karma.js
|
||||
files: [
|
||||
{
|
||||
pattern: `./${config.dir_test}/test-bundler.js`,
|
||||
watched: false,
|
||||
served: true,
|
||||
included: true
|
||||
}
|
||||
],
|
||||
singleRun: !argv.watch,
|
||||
frameworks: ['mocha'],
|
||||
reporters: ['mocha'],
|
||||
preprocessors: {
|
||||
[`${config.dir_test}/test-bundler.js`]: ['webpack']
|
||||
},
|
||||
browsers: ['PhantomJS'],
|
||||
webpack: {
|
||||
devtool: 'cheap-module-source-map',
|
||||
resolve: {
|
||||
...webpackConfig.resolve,
|
||||
alias: {
|
||||
...webpackConfig.resolve.alias,
|
||||
sinon: 'sinon/pkg/sinon.js'
|
||||
}
|
||||
},
|
||||
plugins: webpackConfig.plugins,
|
||||
module: {
|
||||
noParse: [
|
||||
/\/sinon\.js/
|
||||
],
|
||||
loaders: webpackConfig.module.loaders.concat([
|
||||
{
|
||||
test: /sinon(\\|\/)pkg(\\|\/)sinon\.js/,
|
||||
loader: 'imports?define=>false,require=>false'
|
||||
}
|
||||
])
|
||||
},
|
||||
// Enzyme fix, see:
|
||||
// https://github.com/airbnb/enzyme/issues/47
|
||||
externals: {
|
||||
...webpackConfig.externals,
|
||||
'react/addons': true,
|
||||
'react/lib/ExecutionEnvironment': true,
|
||||
'react/lib/ReactContext': 'window'
|
||||
},
|
||||
sassLoader: webpackConfig.sassLoader
|
||||
},
|
||||
webpackMiddleware: {
|
||||
noInfo: true
|
||||
},
|
||||
coverageReporter: {
|
||||
reporters: config.coverage_reporters
|
||||
}
|
||||
}
|
||||
|
||||
if (config.globals.__COVERAGE__) {
|
||||
karmaConfig.reporters.push('coverage')
|
||||
karmaConfig.webpack.module.preLoaders = [{
|
||||
test: /\.(js|jsx)$/,
|
||||
include: new RegExp(config.dir_client),
|
||||
loader: 'isparta',
|
||||
exclude: /node_modules/
|
||||
}]
|
||||
}
|
||||
|
||||
// cannot use `export default` because of Karma.
|
||||
module.exports = (cfg) => cfg.set(karmaConfig)
|
||||
@ -0,0 +1,35 @@
|
||||
import webpack from 'webpack'
|
||||
import _debug from 'debug'
|
||||
import config from '../config'
|
||||
|
||||
const debug = _debug('app:build:webpack-compiler')
|
||||
const DEFAULT_STATS_FORMAT = config.compiler_stats
|
||||
|
||||
export default function webpackCompiler (webpackConfig, statsFormat = DEFAULT_STATS_FORMAT) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const compiler = webpack(webpackConfig)
|
||||
|
||||
compiler.run((err, stats) => {
|
||||
const jsonStats = stats.toJson()
|
||||
|
||||
debug('Webpack compile completed.')
|
||||
debug(stats.toString(statsFormat))
|
||||
|
||||
if (err) {
|
||||
debug('Webpack compiler encountered a fatal error.', err)
|
||||
return reject(err)
|
||||
} else if (jsonStats.errors.length > 0) {
|
||||
debug('Webpack compiler encountered errors.')
|
||||
debug(jsonStats.errors.join('\n'))
|
||||
return reject(new Error('Webpack compiler encountered errors'))
|
||||
} else if (jsonStats.warnings.length > 0) {
|
||||
debug('Webpack compiler encountered warnings.')
|
||||
debug(jsonStats.warnings.join('\n'))
|
||||
} else {
|
||||
debug('No errors or warnings encountered.')
|
||||
}
|
||||
resolve(jsonStats)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1,275 @@
|
||||
import webpack from 'webpack'
|
||||
import cssnano from 'cssnano'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import ExtractTextPlugin from 'extract-text-webpack-plugin'
|
||||
import config from '../config'
|
||||
import _debug from 'debug'
|
||||
|
||||
const debug = _debug('app:webpack:config')
|
||||
const paths = config.utils_paths
|
||||
const {__DEV__, __PROD__, __TEST__} = config.globals
|
||||
|
||||
debug('Create configuration.')
|
||||
const webpackConfig = {
|
||||
name: 'client',
|
||||
target: 'web',
|
||||
devtool: config.compiler_devtool,
|
||||
resolve: {
|
||||
root: paths.client(),
|
||||
extensions: ['', '.js', '.jsx', '.json']
|
||||
},
|
||||
module: {}
|
||||
}
|
||||
// ------------------------------------
|
||||
// Entry Points
|
||||
// ------------------------------------
|
||||
const APP_ENTRY_PATHS = [
|
||||
paths.client('main.js')
|
||||
]
|
||||
|
||||
webpackConfig.entry = {
|
||||
app: __DEV__
|
||||
? APP_ENTRY_PATHS.concat(`webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`)
|
||||
: APP_ENTRY_PATHS,
|
||||
vendor: config.compiler_vendor
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Bundle Output
|
||||
// ------------------------------------
|
||||
webpackConfig.output = {
|
||||
filename: `[name].[${config.compiler_hash_type}].js`,
|
||||
path: paths.dist(),
|
||||
publicPath: config.compiler_public_path
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Plugins
|
||||
// ------------------------------------
|
||||
webpackConfig.plugins = [
|
||||
new webpack.DefinePlugin(config.globals),
|
||||
new HtmlWebpackPlugin({
|
||||
template: paths.client('index.html'),
|
||||
hash: false,
|
||||
favicon: paths.client('static/favicon.ico'),
|
||||
filename: 'index.html',
|
||||
inject: 'body',
|
||||
minify: {
|
||||
collapseWhitespace: true
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
if (__DEV__) {
|
||||
debug('Enable plugins for live development (HMR, NoErrors).')
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
)
|
||||
} else if (__PROD__) {
|
||||
debug('Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).')
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
unused: true,
|
||||
dead_code: true,
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Don't split bundles during testing, since we only want import one bundle
|
||||
if (!__TEST__) {
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
names: ['vendor']
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Pre-Loaders
|
||||
// ------------------------------------
|
||||
/*
|
||||
[ NOTE ]
|
||||
We no longer use eslint-loader due to it severely impacting build
|
||||
times for larger projects. `npm run lint` still exists to aid in
|
||||
deploy processes (such as with CI), and it's recommended that you
|
||||
use a linting plugin for your IDE in place of this loader.
|
||||
|
||||
If you do wish to continue using the loader, you can uncomment
|
||||
the code below and run `npm i --save-dev eslint-loader`. This code
|
||||
will be removed in a future release.
|
||||
|
||||
webpackConfig.module.preLoaders = [{
|
||||
test: /\.(js|jsx)$/,
|
||||
loader: 'eslint',
|
||||
exclude: /node_modules/
|
||||
}]
|
||||
|
||||
webpackConfig.eslint = {
|
||||
configFile: paths.base('.eslintrc'),
|
||||
emitWarning: __DEV__
|
||||
}
|
||||
*/
|
||||
|
||||
// ------------------------------------
|
||||
// Loaders
|
||||
// ------------------------------------
|
||||
// JavaScript / JSON
|
||||
webpackConfig.module.loaders = [{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
cacheDirectory: true,
|
||||
plugins: ['transform-runtime'],
|
||||
presets: ['es2015', 'react', 'stage-0']
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
}]
|
||||
|
||||
// ------------------------------------
|
||||
// Style Loaders
|
||||
// ------------------------------------
|
||||
// We use cssnano with the postcss loader, so we tell
|
||||
// css-loader not to duplicate minimization.
|
||||
const BASE_CSS_LOADER = 'css?sourceMap&-minimize'
|
||||
|
||||
// Add any packge names here whose styles need to be treated as CSS modules.
|
||||
// These paths will be combined into a single regex.
|
||||
const PATHS_TO_TREAT_AS_CSS_MODULES = [
|
||||
// 'react-toolbox', (example)
|
||||
]
|
||||
|
||||
// If config has CSS modules enabled, treat this project's styles as CSS modules.
|
||||
if (config.compiler_css_modules) {
|
||||
PATHS_TO_TREAT_AS_CSS_MODULES.push(
|
||||
paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&') // eslint-disable-line
|
||||
)
|
||||
}
|
||||
|
||||
const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
|
||||
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join('|')})`)
|
||||
|
||||
// Loaders for styles that need to be treated as CSS modules.
|
||||
if (isUsingCSSModules) {
|
||||
const cssModulesLoader = [
|
||||
BASE_CSS_LOADER,
|
||||
'modules',
|
||||
'importLoaders=1',
|
||||
'localIdentName=[name]__[local]___[hash:base64:5]'
|
||||
].join('&')
|
||||
|
||||
webpackConfig.module.loaders.push({
|
||||
test: /\.scss$/,
|
||||
include: cssModulesRegex,
|
||||
loaders: [
|
||||
'style',
|
||||
cssModulesLoader,
|
||||
'postcss',
|
||||
'sass?sourceMap'
|
||||
]
|
||||
})
|
||||
|
||||
webpackConfig.module.loaders.push({
|
||||
test: /\.css$/,
|
||||
include: cssModulesRegex,
|
||||
loaders: [
|
||||
'style',
|
||||
cssModulesLoader,
|
||||
'postcss'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// Loaders for files that should not be treated as CSS modules.
|
||||
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
|
||||
webpackConfig.module.loaders.push({
|
||||
test: /\.scss$/,
|
||||
exclude: excludeCSSModules,
|
||||
loaders: [
|
||||
'style',
|
||||
BASE_CSS_LOADER,
|
||||
'postcss',
|
||||
'sass?sourceMap'
|
||||
]
|
||||
})
|
||||
webpackConfig.module.loaders.push({
|
||||
test: /\.css$/,
|
||||
exclude: excludeCSSModules,
|
||||
loaders: [
|
||||
'style',
|
||||
BASE_CSS_LOADER,
|
||||
'postcss'
|
||||
]
|
||||
})
|
||||
|
||||
// ------------------------------------
|
||||
// Style Configuration
|
||||
// ------------------------------------
|
||||
webpackConfig.sassLoader = {
|
||||
includePaths: paths.client('styles')
|
||||
}
|
||||
|
||||
webpackConfig.postcss = [
|
||||
cssnano({
|
||||
autoprefixer: {
|
||||
add: true,
|
||||
remove: true,
|
||||
browsers: ['last 2 versions']
|
||||
},
|
||||
discardComments: {
|
||||
removeAll: true
|
||||
},
|
||||
discardUnused: false,
|
||||
mergeIdents: false,
|
||||
reduceIdents: false,
|
||||
safe: true,
|
||||
sourcemap: true
|
||||
})
|
||||
]
|
||||
|
||||
// File loaders
|
||||
/* eslint-disable */
|
||||
webpackConfig.module.loaders.push(
|
||||
{ test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
|
||||
{ test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
|
||||
{ test: /\.otf(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' },
|
||||
{ test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
|
||||
{ test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
|
||||
{ test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
|
||||
{ test: /\.(png|jpg)$/, loader: 'url?limit=8192' }
|
||||
)
|
||||
/* eslint-enable */
|
||||
|
||||
// ------------------------------------
|
||||
// Finalize Configuration
|
||||
// ------------------------------------
|
||||
// when we don't know the public path (we know it only when HMR is enabled [in development]) we
|
||||
// need to use the extractTextPlugin to fix this issue:
|
||||
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
|
||||
if (!__DEV__) {
|
||||
debug('Apply ExtractTextPlugin to CSS loaders.')
|
||||
webpackConfig.module.loaders.filter((loader) =>
|
||||
loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0]))
|
||||
).forEach((loader) => {
|
||||
const [first, ...rest] = loader.loaders
|
||||
loader.loader = ExtractTextPlugin.extract(first, rest.join('!'))
|
||||
Reflect.deleteProperty(loader, 'loaders')
|
||||
})
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new ExtractTextPlugin('[name].[contenthash].css', {
|
||||
allChunks: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default webpackConfig
|
||||
@ -0,0 +1,36 @@
|
||||
// Here is where you can define configuration overrides based on the execution environment.
|
||||
// Supply a key to the default export matching the NODE_ENV that you wish to target, and
|
||||
// the base configuration will apply your overrides before exporting itself.
|
||||
export default {
|
||||
// ======================================================
|
||||
// Overrides when NODE_ENV === 'development'
|
||||
// ======================================================
|
||||
// NOTE: In development, we use an explicit public path when the assets
|
||||
// are served webpack by to fix this issue:
|
||||
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
|
||||
development: (config) => ({
|
||||
compiler_public_path: `http://${config.server_host}:${config.server_port}/`,
|
||||
proxy: {
|
||||
enabled: false,
|
||||
options: {
|
||||
host: 'http://localhost:8000',
|
||||
match: /^\/api\/.*/
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
// ======================================================
|
||||
// Overrides when NODE_ENV === 'production'
|
||||
// ======================================================
|
||||
production: (config) => ({
|
||||
compiler_public_path: '/',
|
||||
compiler_fail_on_warning: false,
|
||||
compiler_hash_type: 'chunkhash',
|
||||
compiler_devtool: null,
|
||||
compiler_stats: {
|
||||
chunks: true,
|
||||
chunkModules: true,
|
||||
colors: true
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
/* eslint key-spacing:0 spaced-comment:0 */
|
||||
import path from 'path'
|
||||
import _debug from 'debug'
|
||||
import { argv } from 'yargs'
|
||||
import ip from 'ip'
|
||||
|
||||
const localip = ip.address()
|
||||
const debug = _debug('app:config')
|
||||
debug('Creating default configuration.')
|
||||
|
||||
// ========================================================
|
||||
// Default Configuration
|
||||
// ========================================================
|
||||
const config = {
|
||||
env : process.env.NODE_ENV || 'development',
|
||||
|
||||
// ----------------------------------
|
||||
// Project Structure
|
||||
// ----------------------------------
|
||||
path_base : path.resolve(__dirname, '..'),
|
||||
dir_client : 'src',
|
||||
dir_dist : 'dist',
|
||||
dir_server : 'server',
|
||||
dir_test : 'tests',
|
||||
|
||||
// ----------------------------------
|
||||
// Server Configuration
|
||||
// ----------------------------------
|
||||
server_host : localip, // use string 'localhost' to prevent exposure on local network
|
||||
server_port : process.env.PORT || 3000,
|
||||
|
||||
// ----------------------------------
|
||||
// Compiler Configuration
|
||||
// ----------------------------------
|
||||
compiler_css_modules : true,
|
||||
compiler_devtool : 'source-map',
|
||||
compiler_hash_type : 'hash',
|
||||
compiler_fail_on_warning : false,
|
||||
compiler_quiet : false,
|
||||
compiler_public_path : '/',
|
||||
compiler_stats : {
|
||||
chunks : false,
|
||||
chunkModules : false,
|
||||
colors : true
|
||||
},
|
||||
compiler_vendor : [
|
||||
'babel-polyfill',
|
||||
'history',
|
||||
'react',
|
||||
'react-redux',
|
||||
'react-router',
|
||||
'react-router-redux',
|
||||
'redux'
|
||||
],
|
||||
|
||||
// ----------------------------------
|
||||
// Test Configuration
|
||||
// ----------------------------------
|
||||
coverage_reporters : [
|
||||
{ type : 'text-summary' },
|
||||
{ type : 'lcov', dir : 'coverage' }
|
||||
]
|
||||
}
|
||||
|
||||
/************************************************
|
||||
-------------------------------------------------
|
||||
|
||||
All Internal Configuration Below
|
||||
Edit at Your Own Risk
|
||||
|
||||
-------------------------------------------------
|
||||
************************************************/
|
||||
|
||||
// ------------------------------------
|
||||
// Environment
|
||||
// ------------------------------------
|
||||
// N.B.: globals added here must _also_ be added to .eslintrc
|
||||
config.globals = {
|
||||
'process.env' : {
|
||||
'NODE_ENV' : JSON.stringify(config.env)
|
||||
},
|
||||
'NODE_ENV' : config.env,
|
||||
'__DEV__' : config.env === 'development',
|
||||
'__PROD__' : config.env === 'production',
|
||||
'__TEST__' : config.env === 'test',
|
||||
'__DEBUG__' : config.env === 'development' && !argv.no_debug,
|
||||
'__COVERAGE__' : !argv.watch && config.env === 'test',
|
||||
'__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Validate Vendor Dependencies
|
||||
// ------------------------------------
|
||||
const pkg = require('../package.json')
|
||||
|
||||
config.compiler_vendor = config.compiler_vendor
|
||||
.filter((dep) => {
|
||||
if (pkg.dependencies[dep]) return true
|
||||
|
||||
debug(
|
||||
`Package "${dep}" was not found as an npm dependency in package.json; ` +
|
||||
`it won't be included in the webpack vendor bundle.
|
||||
Consider removing it from vendor_dependencies in ~/config/index.js`
|
||||
)
|
||||
})
|
||||
|
||||
// ------------------------------------
|
||||
// Utilities
|
||||
// ------------------------------------
|
||||
const resolve = path.resolve
|
||||
const base = (...args) =>
|
||||
Reflect.apply(resolve, null, [config.path_base, ...args])
|
||||
|
||||
config.utils_paths = {
|
||||
base : base,
|
||||
client : base.bind(null, config.dir_client),
|
||||
dist : base.bind(null, config.dir_dist)
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Environment Configuration
|
||||
// ========================================================
|
||||
debug(`Looking for environment overrides for NODE_ENV "${config.env}".`)
|
||||
const environments = require('./environments').default
|
||||
const overrides = environments[config.env]
|
||||
if (overrides) {
|
||||
debug('Found overrides, applying to default configuration.')
|
||||
Object.assign(config, overrides(config))
|
||||
} else {
|
||||
debug('No environment overrides found, defaults will be used.')
|
||||
}
|
||||
|
||||
export default config
|
||||
@ -0,0 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"verbose": false,
|
||||
"ignore": ["dist", "coverage", "tests", "src"]
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "ambar-frontend",
|
||||
"version": "0.0.1",
|
||||
"description": "Ambar Frontend",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">=4.2.0",
|
||||
"npm": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"compile": "better-npm-run compile",
|
||||
"lint": "eslint src tests server",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"start": "better-npm-run start",
|
||||
"dev": "better-npm-run dev",
|
||||
"dev:no-debug": "npm run dev -- --no_debug",
|
||||
"deploy": "better-npm-run deploy",
|
||||
"deploy:dev": "better-npm-run deploy:dev",
|
||||
"deploy:prod": "better-npm-run deploy:prod"
|
||||
},
|
||||
"betterScripts": {
|
||||
"compile": {
|
||||
"command": "babel-node bin/compile",
|
||||
"env": {
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
"command": "nodemon --exec babel-node bin/server",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"command": "npm run clean && npm run compile",
|
||||
"env": {
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
},
|
||||
"deploy:dev": {
|
||||
"command": "npm run deploy",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
},
|
||||
"deploy:prod": {
|
||||
"command": "npm run deploy",
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
},
|
||||
"start": {
|
||||
"command": "babel-node bin/server",
|
||||
"env": {
|
||||
"DEBUG": "app:*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"author": "RD17 <hello@ambar.cloud> (https://ambar.cloud)",
|
||||
"license": "Fair Source",
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^1.32.0",
|
||||
"babel-cli": "^6.5.1",
|
||||
"babel-core": "^6.3.17",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-plugin-transform-runtime": "^6.3.13",
|
||||
"babel-polyfill": "^6.9.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-es2015-loose": "^7.0.0",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-0": "^6.3.13",
|
||||
"babel-register": "^6.3.13",
|
||||
"babel-runtime": "^6.3.19",
|
||||
"better-npm-run": "0.0.10",
|
||||
"brace": "^0.9.0",
|
||||
"css-loader": "^0.23.0",
|
||||
"cssnano": "^3.3.2",
|
||||
"debug": "^2.2.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"extract-text-webpack-plugin": "^1.0.0",
|
||||
"file-loader": "^0.9.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"history": "^2.0.0",
|
||||
"html-webpack-plugin": "^2.7.1",
|
||||
"immutability-helper": "^2.5.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"ip": "^1.1.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"koa": "^2.0.0-alpha.3",
|
||||
"koa-connect-history-api-fallback": "^0.3.0",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-proxy": "^0.6.0",
|
||||
"koa-static": "^3.0.0",
|
||||
"material-ui": "^0.18.1",
|
||||
"moment": "^2.14.1",
|
||||
"node-sass": "^3.7.0",
|
||||
"postcss-loader": "^0.9.0",
|
||||
"react": "^15.0.0",
|
||||
"react-ace": "^4.1.0",
|
||||
"react-addons-css-transition-group": "^15.6.2",
|
||||
"react-autosuggest": "^9.0.1",
|
||||
"react-dom": "^15.0.0",
|
||||
"react-dropzone": "^3.9.2",
|
||||
"react-input-autosize": "^1.1.4",
|
||||
"react-input-mask": "^0.7.2",
|
||||
"react-redux": "^4.0.0",
|
||||
"react-responsive": "^1.2.6",
|
||||
"react-router": "^2.2.0",
|
||||
"react-router-redux": "^4.0.0",
|
||||
"react-spinkit": "^1.1.11",
|
||||
"react-tap-event-plugin": "^2.0.1",
|
||||
"recharts": "1.0.0-beta.1",
|
||||
"redux": "^3.0.0",
|
||||
"redux-thunk": "^2.0.0",
|
||||
"rimraf": "^2.5.1",
|
||||
"sass-loader": "^4.0.0",
|
||||
"style-loader": "^0.13.0",
|
||||
"url-loader": "^0.5.6",
|
||||
"validator": "^5.5.0",
|
||||
"webpack": "^1.12.14",
|
||||
"whatwg-fetch": "^2.0.1",
|
||||
"yargs": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^6.0.0-beta.6",
|
||||
"eslint": "^3.0.1",
|
||||
"eslint-config-standard": "^5.1.0",
|
||||
"eslint-config-standard-react": "^3.0.0",
|
||||
"eslint-plugin-babel": "^3.2.0",
|
||||
"eslint-plugin-promise": "^2.0.0",
|
||||
"eslint-plugin-react": "^6.0.0",
|
||||
"eslint-plugin-standard": "^2.0.0",
|
||||
"isparta-loader": "^2.0.0",
|
||||
"nodemon": "^1.8.1",
|
||||
"react-addons-test-utils": "^15.0.0",
|
||||
"redbox-react": "^1.2.10",
|
||||
"webpack-dev-middleware": "^1.6.1",
|
||||
"webpack-hot-middleware": "^2.6.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
// Based on: https://github.com/dayAlone/koa-webpack-hot-middleware/blob/master/index.js
|
||||
export default function applyExpressMiddleware (fn, req, res) {
|
||||
const originalEnd = res.end
|
||||
|
||||
return new Promise((resolve) => {
|
||||
res.end = function () {
|
||||
originalEnd.apply(this, arguments)
|
||||
resolve(false)
|
||||
}
|
||||
fn(req, res, function () {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
import Koa from 'koa'
|
||||
import convert from 'koa-convert'
|
||||
import webpack from 'webpack'
|
||||
import webpackConfig from '../build/webpack.config'
|
||||
import historyApiFallback from 'koa-connect-history-api-fallback'
|
||||
import serve from 'koa-static'
|
||||
import proxy from 'koa-proxy'
|
||||
import _debug from 'debug'
|
||||
import config from '../config'
|
||||
import webpackDevMiddleware from './middleware/webpack-dev'
|
||||
import webpackHMRMiddleware from './middleware/webpack-hmr'
|
||||
|
||||
const debug = _debug('app:server')
|
||||
const paths = config.utils_paths
|
||||
const app = new Koa()
|
||||
|
||||
// Enable koa-proxy if it has been enabled in the config.
|
||||
if (config.proxy && config.proxy.enabled) {
|
||||
app.use(convert(proxy(config.proxy.options)))
|
||||
}
|
||||
|
||||
// This rewrites all routes requests to the root /index.html file
|
||||
// (ignoring file requests). If you want to implement isomorphic
|
||||
// rendering, you'll want to remove this middleware.
|
||||
app.use(convert(historyApiFallback({
|
||||
verbose: false
|
||||
})))
|
||||
|
||||
// ------------------------------------
|
||||
// Apply Webpack HMR Middleware
|
||||
// ------------------------------------
|
||||
if (config.env === 'development') {
|
||||
const compiler = webpack(webpackConfig)
|
||||
|
||||
// Enable webpack-dev and webpack-hot middleware
|
||||
const { publicPath } = webpackConfig.output
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, publicPath))
|
||||
app.use(webpackHMRMiddleware(compiler))
|
||||
|
||||
// Serve static assets from ~/src/static since Webpack is unaware of
|
||||
// these files. This middleware doesn't need to be enabled outside
|
||||
// of development since this directory will be copied into ~/dist
|
||||
// when the application is compiled.
|
||||
app.use(serve(paths.client('static')))
|
||||
} else {
|
||||
debug(
|
||||
'Server is being run outside of live development mode, meaning it will ' +
|
||||
'only serve the compiled application bundle in ~/dist. Generally you ' +
|
||||
'do not need an application server for this and can instead use a web ' +
|
||||
'server such as nginx to serve your static files. See the "deployment" ' +
|
||||
'section in the README for more information on deployment strategies.'
|
||||
)
|
||||
|
||||
// Serving ~/dist by default. Ideally these files should be served by
|
||||
// the web server and not the app server, but this helps to demo the
|
||||
// server in production.
|
||||
app.use(serve(paths.dist()))
|
||||
}
|
||||
|
||||
export default app
|
||||
@ -0,0 +1,34 @@
|
||||
import WebpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import applyExpressMiddleware from '../lib/apply-express-middleware'
|
||||
import _debug from 'debug'
|
||||
import config from '../../config'
|
||||
|
||||
const paths = config.utils_paths
|
||||
const debug = _debug('app:server:webpack-dev')
|
||||
|
||||
export default function (compiler, publicPath) {
|
||||
debug('Enable webpack dev middleware.')
|
||||
|
||||
const middleware = WebpackDevMiddleware(compiler, {
|
||||
publicPath,
|
||||
contentBase: paths.client(),
|
||||
hot: true,
|
||||
quiet: config.compiler_quiet,
|
||||
noInfo: config.compiler_quiet,
|
||||
lazy: false,
|
||||
stats: config.compiler_stats
|
||||
})
|
||||
|
||||
return async function koaWebpackDevMiddleware (ctx, next) {
|
||||
let hasNext = await applyExpressMiddleware(middleware, ctx.req, {
|
||||
end: (content) => (ctx.body = content),
|
||||
setHeader: function () {
|
||||
ctx.set.apply(ctx, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
if (hasNext) {
|
||||
await next()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import WebpackHotMiddleware from 'webpack-hot-middleware'
|
||||
import applyExpressMiddleware from '../lib/apply-express-middleware'
|
||||
import _debug from 'debug'
|
||||
|
||||
const debug = _debug('app:server:webpack-hmr')
|
||||
|
||||
export default function (compiler, opts) {
|
||||
debug('Enable Webpack Hot Module Replacement (HMR).')
|
||||
|
||||
const middleware = WebpackHotMiddleware(compiler, opts)
|
||||
return async function koaWebpackHMR (ctx, next) {
|
||||
let hasNext = await applyExpressMiddleware(middleware, ctx.req, ctx.res)
|
||||
|
||||
if (hasNext && next) {
|
||||
await next()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import classes from './AmbarResponsiveLogo.scss'
|
||||
import MediaQuery from 'react-responsive'
|
||||
|
||||
export const AmbarResponsiveLogo = ({mode, version}) => (
|
||||
<div className={classes.ambarResponsiveLogo} title={`Ambar ${mode.toUpperCase()} ${version}`} >
|
||||
<img alt='Logo'
|
||||
src={'owl.svg'} />
|
||||
</div>)
|
||||
|
||||
|
||||
AmbarResponsiveLogo.propTypes = {
|
||||
mode: React.PropTypes.string.isRequired,
|
||||
version: React.PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default AmbarResponsiveLogo
|
||||
@ -0,0 +1,8 @@
|
||||
.ambarResponsiveLogo {
|
||||
height: 48px;
|
||||
margin-bottom: 8px;
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
fill: black;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import AmbarResponsiveLogo from './AmbarResponsiveLogo'
|
||||
|
||||
export default AmbarResponsiveLogo
|
||||
@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import { Card, CardText } from 'material-ui/Card'
|
||||
import FullScreenPattern from '../FullScreenPattern'
|
||||
import classes from './AuthPageTemplate.scss'
|
||||
|
||||
export const AuthPageTemplate = ({ children }) => (
|
||||
<FullScreenPattern>
|
||||
<Paper zDepth={5} style={{ backgroundColor: 'white' }}>
|
||||
<Card containerStyle={{ padding: 0, minWidth: '300px' }}>
|
||||
<CardText style={{ display: 'flex', justifyContent: 'center', paddingBottom: '0' }}>
|
||||
<a href='https://ambar.cloud' target='_blank'><img height={100} src='owl-green.svg' alt='logo' /></a>
|
||||
</CardText>
|
||||
{children}
|
||||
</Card>
|
||||
</Paper>
|
||||
</FullScreenPattern>
|
||||
)
|
||||
|
||||
export default AuthPageTemplate
|
||||
@ -0,0 +1,2 @@
|
||||
import AuthPageTemplate from'./AuthPageTemplate'
|
||||
export default AuthPageTemplate
|
||||
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react'
|
||||
import HighlightedSpan from '../HighlightedSpan'
|
||||
|
||||
import classes from './AuthorLabel.scss'
|
||||
|
||||
const AuthorLabel = ({ content, performSearchByAuthor, ...otherProps }) => {
|
||||
const authorHighlighted = content.highlight && content.highlight.author ? true : false
|
||||
|
||||
return (
|
||||
<HighlightedSpan
|
||||
onTouchTap={() => performSearchByAuthor(`${content.author}*`)}
|
||||
isClickable={true}
|
||||
isHighlighted={authorHighlighted}
|
||||
{...otherProps}>
|
||||
{content.author}
|
||||
</HighlightedSpan>
|
||||
)
|
||||
}
|
||||
|
||||
AuthorLabel.propTypes = {
|
||||
content: React.PropTypes.object.isRequired,
|
||||
performSearchByAuthor: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default AuthorLabel
|
||||
@ -0,0 +1,3 @@
|
||||
import AuthorLabel from './AuthorLabel'
|
||||
|
||||
export default AuthorLabel
|
||||
@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react'
|
||||
import classes from './ClickableFilePath.scss'
|
||||
|
||||
const ClickableFilePath = (props) => {
|
||||
const { meta, performSearchByPathToFile } = props
|
||||
|
||||
const fullPath = meta.full_name
|
||||
|
||||
const fullPathParts = fullPath.split('/')
|
||||
.filter(part => part != '')
|
||||
const fullPathPartsExtended = fullPathParts
|
||||
.map((part, idx) => {
|
||||
const isLast = idx === fullPathParts.length - 1
|
||||
const trailingSymbol = isLast ? '' : '/'
|
||||
const trailingAsterisk = '*'
|
||||
|
||||
return {
|
||||
part: `${part}${trailingSymbol}`,
|
||||
pathToPart: `//${fullPathParts.filter((part, innerIdx) => innerIdx <= idx).join('/')}${trailingAsterisk}`
|
||||
}
|
||||
})
|
||||
|
||||
const isHighlighted = meta.highlight && meta.highlight.full_name
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={isHighlighted ? classes.metaFullNameLineContainerHighlighted : classes.metaFullNameLineContainer}>
|
||||
<span>//</span>
|
||||
{fullPathPartsExtended.map((part, idx) => <span
|
||||
className={classes.metaFullNamePart}
|
||||
key={idx}
|
||||
onTouchTap={() => performSearchByPathToFile(part.pathToPart)}>
|
||||
{part.part}
|
||||
</span>)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ClickableFilePath.propTypes = {
|
||||
meta: React.PropTypes.object.isRequired,
|
||||
performSearchByPathToFile: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default ClickableFilePath
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
div.metaFullNameLineContainer {
|
||||
color: #9E9E9E;
|
||||
font-size: 12px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
div.metaFullNameLineContainerHighlighted {
|
||||
border-radius: 4px;
|
||||
background-color: #fff176;
|
||||
font-size: 12px;
|
||||
display: inline;
|
||||
color: rgb(158, 158, 158);
|
||||
}
|
||||
|
||||
span.metaFullNamePart {
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
|
||||
span.metaFullNamePart:hover {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #B2EBF2;
|
||||
}
|
||||
|
||||
span.metaFullNamePart:active {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #80DEEA;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import ClickableFilePath from './ClickableFilePath'
|
||||
|
||||
export default ClickableFilePath
|
||||
@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react'
|
||||
import classes from './CodeEditor.scss'
|
||||
|
||||
import brace from 'brace'
|
||||
import AceEditor from 'react-ace'
|
||||
import 'brace/mode/javascript'
|
||||
import 'brace/theme/monokai'
|
||||
|
||||
const CodeEditor = (props) => {
|
||||
const { uId, value, onChange, readOnly, systemMessage } = props
|
||||
return (
|
||||
<div>
|
||||
<AceEditor
|
||||
mode="javascript"
|
||||
theme="monokai"
|
||||
onChange={onChange}
|
||||
name={uId}
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
width='100%'
|
||||
heigth= '100%'
|
||||
fontSize={14}
|
||||
wrapEnabled={true}
|
||||
showPrintMargin={false}
|
||||
/>
|
||||
<p className={classes.codeEditorSystemMessage}>{systemMessage}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CodeEditor.propTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
readOnly: React.PropTypes.bool.isRequired,
|
||||
systemMessage: React.PropTypes.string
|
||||
}
|
||||
|
||||
export default CodeEditor
|
||||
@ -0,0 +1,16 @@
|
||||
p.codeEditorSystemMessage {
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
background-color: #272822;
|
||||
color: #FF3333;
|
||||
font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
font-size: 14px;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
font-variant-caps: normal;
|
||||
font-variant-ligatures: normal;
|
||||
font-variant-numeric: normal;
|
||||
font-weight: normal;
|
||||
height: 25px;
|
||||
margin: 0;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import CodeEditor from './CodeEditor'
|
||||
|
||||
export default CodeEditor
|
||||
@ -0,0 +1,50 @@
|
||||
import React, { Component } from 'react'
|
||||
import Avatar from 'material-ui/Avatar'
|
||||
import { files } from 'utils/'
|
||||
|
||||
import classes from './FileAvatar.scss'
|
||||
|
||||
const getHashCode = (str) => {
|
||||
let hash = 0;
|
||||
|
||||
if (str.length == 0) {
|
||||
return hash
|
||||
}
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i)
|
||||
hash = ((hash << 5) - hash) + char
|
||||
hash = hash & hash // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
const FileAvatar = ({ meta, searchFunction }) => {
|
||||
const colors = [
|
||||
'#EF5350', '#E53935', '#D81B60', '#EC407A', '#AB47BC', '#7E57C2', '#5C6BC0', '#2196F3', '#43A047', '#EF6C00', '#A1887F', '#78909C', '#FF4081', '#3949AB']
|
||||
|
||||
let extension = files.getExtension(meta)
|
||||
|
||||
const avatarStyle = {
|
||||
fontSize: '12px',
|
||||
textTransform: 'uppercase',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
className={classes.resultAvatar}
|
||||
onTouchTap={() => searchFunction(`*.${extension}`)}
|
||||
size={38}
|
||||
style={avatarStyle}
|
||||
backgroundColor={colors[getHashCode(extension) % colors.length]}>{extension}</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
FileAvatar.propTypes = {
|
||||
meta: React.PropTypes.object.isRequired,
|
||||
searchFunction: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default FileAvatar
|
||||
@ -0,0 +1,19 @@
|
||||
span.clickable {
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
|
||||
span.clickable:hover {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #B2EBF2;
|
||||
}
|
||||
|
||||
span.clickable:active {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #80DEEA;
|
||||
}
|
||||
|
||||
span.highlighted {
|
||||
background-color: #fff176;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import FileAvatar from './FileAvatar'
|
||||
|
||||
export default FileAvatar
|
||||
@ -0,0 +1,23 @@
|
||||
import React, { Component } from 'react'
|
||||
import { files } from 'utils/'
|
||||
import HighlightedSpan from '../HighlightedSpan'
|
||||
|
||||
import classes from './FileSizeLabel.scss'
|
||||
|
||||
const FileSizeLabel = ({ content, searchQuery, ...otherProps }) => {
|
||||
const SIZE_QUERY = /((^|\s)size(>|<)[=]{0,1})([0-9]*)([k|m]{0,1})/im
|
||||
|
||||
const sizeHighlighted = content.size ? SIZE_QUERY.test(searchQuery) : false
|
||||
const size = content.size
|
||||
|
||||
return (
|
||||
<HighlightedSpan isHighlighted={sizeHighlighted} {...otherProps}>{files.formatFileSize(size)}</HighlightedSpan>
|
||||
)
|
||||
}
|
||||
|
||||
FileSizeLabel.propTypes = {
|
||||
content: React.PropTypes.object.isRequired,
|
||||
searchQuery: React.PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default FileSizeLabel
|
||||
@ -0,0 +1,3 @@
|
||||
import FileSizeLabel from './FileSizeLabel'
|
||||
|
||||
export default FileSizeLabel
|
||||
@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import CircularProgress from 'material-ui/CircularProgress'
|
||||
import FullScreenPattern from '../FullScreenPattern'
|
||||
import classes from './FullScreenLoader.scss'
|
||||
|
||||
export const FullScreenLoader = (props) =>
|
||||
<FullScreenPattern>
|
||||
<CircularProgress color='white' size={120} thickness={5} />
|
||||
</FullScreenPattern>
|
||||
|
||||
export default FullScreenLoader
|
||||
@ -0,0 +1,2 @@
|
||||
import FullScreenLoader from'./FullScreenLoader'
|
||||
export default FullScreenLoader
|
||||
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import classes from './FullScreenPattern.scss'
|
||||
|
||||
export const FullScreenPattern = ({children}) =>
|
||||
<div className={classes.solidBackground}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
export default FullScreenPattern
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
||||
import FullScreenPattern from'./FullScreenPattern'
|
||||
export default FullScreenPattern
|
||||
@ -0,0 +1,15 @@
|
||||
import React, { Component } from 'react'
|
||||
import classes from './HighlightedSpan.scss'
|
||||
|
||||
const HighlightedSpan = ({children, isHighlighted, isClickable = false, ...otherProps}) => {
|
||||
const classNames = `${isHighlighted ? classes.highlighted : '' } ${isClickable ? classes.clickable : ''}`
|
||||
return <span className={classNames} {...otherProps}>{children}</span>
|
||||
}
|
||||
|
||||
HighlightedSpan.propTypes = {
|
||||
children: React.PropTypes.any,
|
||||
isHighlighted: React.PropTypes.bool.isRequired,
|
||||
isClickable: React.PropTypes.bool
|
||||
}
|
||||
|
||||
export default HighlightedSpan
|
||||
@ -0,0 +1,20 @@
|
||||
span.clickable {
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
|
||||
span.clickable:hover {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #B2EBF2;
|
||||
}
|
||||
|
||||
span.clickable:active {
|
||||
cursor: pointer; cursor: hand;
|
||||
border-radius: 4px;
|
||||
background-color: #80DEEA;
|
||||
}
|
||||
|
||||
span.highlighted {
|
||||
background-color: #fff176;
|
||||
color: rgb(158, 158, 158);
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import HighlightedSpan from './HighlightedSpan'
|
||||
|
||||
export default HighlightedSpan
|
||||
@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import deepEqual from 'deep-equal'
|
||||
import { bindMethods } from './bindMethods.js'
|
||||
|
||||
export default class InfiniteScroll extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
bindMethods(this, ['scrollListener', 'attachScrollListener', 'detachScrollListener'])
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.attachScrollListener()
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !deepEqual(this.props.children, nextProps.children)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.detachScrollListener()
|
||||
}
|
||||
|
||||
render() {
|
||||
return null
|
||||
}
|
||||
|
||||
safeCallOnScrollDown(isFirstPage) {
|
||||
if (this.props.onScrollDown) {
|
||||
this.props.onScrollDown(isFirstPage)
|
||||
}
|
||||
}
|
||||
|
||||
scrollListener() {
|
||||
const { hasMore, currentPage, anchorEl, threshold, loadMore } = this.props
|
||||
let el = anchorEl
|
||||
|
||||
this.safeCallOnScrollDown(el.scrollTop < el.offsetHeight * 0.75)
|
||||
|
||||
if (el.scrollHeight > 0 && (el.scrollHeight - el.scrollTop - el.offsetHeight < threshold)) {
|
||||
{ hasMore && loadMore(currentPage + 1) }
|
||||
}
|
||||
}
|
||||
|
||||
attachScrollListener() {
|
||||
const el = this.props.anchorEl
|
||||
|
||||
el.addEventListener('scroll', this.scrollListener)
|
||||
el.addEventListener('resize', this.scrollListener)
|
||||
this.scrollListener()
|
||||
}
|
||||
|
||||
detachScrollListener() {
|
||||
const el = this.props.anchorEl
|
||||
|
||||
el.removeEventListener('scroll', this.scrollListener)
|
||||
el.removeEventListener('resize', this.scrollListener)
|
||||
}
|
||||
}
|
||||
|
||||
InfiniteScroll.defaultProps = {
|
||||
currentPage: 0,
|
||||
hasMore: true,
|
||||
threshold: 250
|
||||
}
|
||||
|
||||
InfiniteScroll.PropTypes = {
|
||||
currentPage: React.PropTypes.number.isRequired,
|
||||
hasMore: React.PropTypes.bool,
|
||||
loadMore: React.PropTypes.func.isRequired,
|
||||
threshold: React.PropTypes.number,
|
||||
onScrollDown: React.PropTypes.func,
|
||||
anchorEl: React.PropTypes.object.isRequired
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export function bindMethods(component, names) {
|
||||
if(typeof names === 'string') {
|
||||
names = [names];
|
||||
}
|
||||
names.forEach((name) =>
|
||||
component[name] = component[name].bind(component)
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import InfiniteScroll from './InfiniteScroll'
|
||||
|
||||
export default InfiniteScroll
|
||||
@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import CircularProgress from 'material-ui/CircularProgress'
|
||||
import classes from './LoadingIndicator.scss'
|
||||
|
||||
const LoadingIndicator = (props) => {
|
||||
const { small, medium, large, freezed = false, blurred = false, color = '#00bcd4' } = props
|
||||
const indicatorSize = (small ? 23 : (medium ? 40 : (large ? 50 : 40)))
|
||||
const mode = freezed ? 'determinate' : 'indeterminate'
|
||||
const className = blurred ? classes.circularProgressBlurred : classes.circularProgress
|
||||
return <div className={className}>
|
||||
<CircularProgress size={indicatorSize} mode={mode} value={100} color={color} />
|
||||
</div>
|
||||
}
|
||||
|
||||
LoadingIndicator.propTypes = {
|
||||
small: React.PropTypes.bool,
|
||||
medium: React.PropTypes.bool,
|
||||
large: React.PropTypes.bool,
|
||||
freezed: React.PropTypes.bool
|
||||
}
|
||||
|
||||
export default LoadingIndicator
|
||||
@ -0,0 +1,17 @@
|
||||
div.circularProgress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div.circularProgressBlurred {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
-webkit-filter: blur(3px);
|
||||
filter: blur(3px);
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import LoadingIndicator from './LoadingIndicator'
|
||||
|
||||
export default LoadingIndicator
|
||||
@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import Snackbar from 'material-ui/Snackbar'
|
||||
import classes from './NotificationIndicator.scss'
|
||||
|
||||
export const NotificationIndicator = ({close, isOpen, message, reason}) => {
|
||||
const ERROR_COLOR = '#FF3333'
|
||||
const INFO_COLOR = '#8BC34A'
|
||||
|
||||
const isError = reason === 'error'
|
||||
const textColor = isError ? ERROR_COLOR : INFO_COLOR
|
||||
|
||||
return (<Snackbar
|
||||
open={isOpen}
|
||||
message={message}
|
||||
onRequestClose={ () => close() }
|
||||
contentStyle={{ color: textColor, fontWeight: '600', display: 'flex', justifyContent: 'center', userSelect: 'none', cursor: 'default' }}
|
||||
/>)
|
||||
}
|
||||
|
||||
NotificationIndicator.propTypes = {
|
||||
close: React.PropTypes.func.isRequired,
|
||||
message: React.PropTypes.string.isRequired,
|
||||
isOpen: React.PropTypes.bool.isRequired,
|
||||
reason: React.PropTypes.string
|
||||
}
|
||||
export default NotificationIndicator
|
||||
@ -0,0 +1,2 @@
|
||||
import NotificationIndicator from'./NotificationIndicator'
|
||||
export default NotificationIndicator
|
||||
@ -0,0 +1,241 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
import AutosizeInput from 'react-input-autosize'
|
||||
import Autosuggest from 'react-autosuggest'
|
||||
import FontIcon from 'material-ui/FontIcon'
|
||||
|
||||
import { Tag } from './components'
|
||||
import classes from './TagsInput.scss'
|
||||
|
||||
class TagsInput extends Component {
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
suggestions: []
|
||||
}
|
||||
}
|
||||
|
||||
addTag(tagName) {
|
||||
if (!tagName) {
|
||||
return
|
||||
}
|
||||
|
||||
const tagType = 'manual'
|
||||
|
||||
if (!this.props.tags.find(t => (t.name === tagName))) {
|
||||
this.props.onAddTag(tagType, tagName)
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, inputValue: '' })
|
||||
}
|
||||
|
||||
removeTag(tagType, tagName) {
|
||||
const currentTags = this.props.tags.map(t => t.name)
|
||||
if (!currentTags || currentTags.length === 0) {
|
||||
return
|
||||
}
|
||||
this.props.onRemoveTag(tagType, tagName)
|
||||
}
|
||||
|
||||
removeLastTag() {
|
||||
const currentTags = this.props.tags
|
||||
if (!currentTags || currentTags.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const lastTag = currentTags[currentTags.length - 1]
|
||||
if (lastTag.isFetching) {
|
||||
return
|
||||
}
|
||||
|
||||
this.props.onRemoveTag(lastTag.type, lastTag.name)
|
||||
}
|
||||
|
||||
onChange(value) {
|
||||
this.setState({ ...this.state, inputValue: value.trim() })
|
||||
value = value.replace(/[\s\,\;]/gim, ',')
|
||||
|
||||
const newTags = value.split(',').map(t => t.trim().toLowerCase())
|
||||
|
||||
if (newTags.length > 1 && newTags[0] != '') {
|
||||
this.addTag(newTags[0])
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPress(event) {
|
||||
if (event.charCode === 13) { // onEnter
|
||||
this.addTag(this.state.inputValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
if (event.keyCode === 8) { // onBackspace
|
||||
if (!this.state.inputValue) {
|
||||
this.removeLastTag()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusOnInput() {
|
||||
if (!this.refs.suggestInput) {
|
||||
return
|
||||
}
|
||||
this.refs.suggestInput.input.focus()
|
||||
}
|
||||
|
||||
autosizeInput(inputProps) {
|
||||
return (
|
||||
<AutosizeInput
|
||||
minWidth={40}
|
||||
inputStyle={{
|
||||
border: 'none',
|
||||
backgroundImage: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
WebkitBoxShadow: 'none',
|
||||
MozBoxShadow: 'none',
|
||||
boxShadow: 'none',
|
||||
outline: 'none',
|
||||
margin: '4px',
|
||||
padding: '0'
|
||||
}}
|
||||
type='text'
|
||||
placeholder=''
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
getSuggestions(value) {
|
||||
|
||||
const inputValue = value.trim().toLowerCase()
|
||||
const inputLength = inputValue.length
|
||||
const suggestions = this.props.suggestions
|
||||
|
||||
return inputLength === 0 ? suggestions.slice(0, 10) : suggestions.filter(suggestion =>
|
||||
suggestion.toLowerCase().slice(0, inputLength) === inputValue
|
||||
).slice(0, 10)
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested({ value }) {
|
||||
this.setState({
|
||||
suggestions: this.getSuggestions(value)
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested = () => {
|
||||
this.setState({
|
||||
suggestions: []
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props
|
||||
|
||||
const theme = {
|
||||
container: {
|
||||
display: 'inline-block',
|
||||
position: 'relative',
|
||||
margin: '4px',
|
||||
overflow: 'visible'
|
||||
},
|
||||
inputOpen: {
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0
|
||||
},
|
||||
suggestionsContainer: {
|
||||
display: 'none'
|
||||
},
|
||||
suggestionsContainerOpen: {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: 25,
|
||||
width: 120,
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: '0 0 7px rgba(0, 0, 0, 0.4)',
|
||||
borderRadius: '2px',
|
||||
fontSize: 12,
|
||||
zIndex: 2
|
||||
},
|
||||
suggestionsList: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
listStyleType: 'none',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
},
|
||||
suggestion: {
|
||||
cursor: 'pointer',
|
||||
padding: '2px 4px'
|
||||
},
|
||||
suggestionHighlighted: {
|
||||
backgroundColor: 'rgba(0, 188, 212, 0.15)'
|
||||
}
|
||||
}
|
||||
|
||||
const { performSearchByTag, showRemoveIcon = true, showAddField = true, style = {} } = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ padding: '5px', cursor: 'text', display: 'flex', flexWrap: 'wrap', ...style }}
|
||||
className='TAGS_CONTAINER'
|
||||
onClick={(e) => {
|
||||
if (e.target.className == 'TAGS_CONTAINER') {
|
||||
this.focusOnInput()
|
||||
}
|
||||
}}>
|
||||
{this.props.tags.map((tag, idx) =>
|
||||
<Tag
|
||||
key={idx}
|
||||
onRemove={(tagType, tagName) => this.removeTag(tagType, tagName)}
|
||||
onClick={(tagName) => performSearchByTag(tagName)}
|
||||
tagName={tag.name}
|
||||
tagType={tag.type}
|
||||
isHighlighted={!!tag.highlight && !!tag.highlight.name}
|
||||
isFetching={tag.isFetching}
|
||||
showRemoveIcon={showRemoveIcon}
|
||||
/>)
|
||||
}
|
||||
{showAddField && <Autosuggest
|
||||
ref='suggestInput'
|
||||
suggestions={this.state.suggestions}
|
||||
getSuggestionValue={s => s}
|
||||
renderSuggestion={(suggestion) => <span>{suggestion}</span>}
|
||||
onSuggestionSelected={(e, { suggestion, suggegstionValue, suggestionIndex, sectionIndex, method }) => {
|
||||
this.addTag(suggestion)
|
||||
}}
|
||||
shouldRenderSuggestions={(value) => true}
|
||||
onSuggestionsFetchRequested={e => this.onSuggestionsFetchRequested(e)}
|
||||
onSuggestionsClearRequested={e => this.onSuggestionsClearRequested(e)}
|
||||
inputProps={{
|
||||
value: this.state.inputValue,
|
||||
onChange: (e, { newValue, method }) => this.onChange(newValue),
|
||||
onKeyPress: (event) => this.onKeyPress(event),
|
||||
onKeyDown: (event) => this.onKeyDown(event)
|
||||
}}
|
||||
theme={theme}
|
||||
renderInputComponent={(inputProps) => this.autosizeInput(inputProps)}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TagsInput.propTypes = {
|
||||
tags: React.PropTypes.array.isRequired,
|
||||
suggestion: React.PropTypes.array,
|
||||
onAddTag: React.PropTypes.func,
|
||||
onRemoveTag: React.PropTypes.func,
|
||||
showRemoveIcon: React.PropTypes.bool,
|
||||
showAddField: React.PropTypes.bool,
|
||||
performSearchByTag: React.PropTypes.func.isRequired,
|
||||
style: React.PropTypes.object
|
||||
}
|
||||
|
||||
export default TagsInput
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
.tagInput {
|
||||
border:none;
|
||||
background-image:none;
|
||||
background-color:transparent;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
import ClearIcon from 'material-ui/svg-icons/content/clear'
|
||||
import { LoadingIndicator } from 'components/BasicComponents'
|
||||
import classes from './Tag.scss'
|
||||
|
||||
const Tag = ({ tagName, tagType, onRemove, onClick, isHighlighted, isFetching, showRemoveIcon = true }) => {
|
||||
const onRemoveCallback = onRemove
|
||||
? onRemove
|
||||
: () => { }
|
||||
|
||||
const onClickCallback = onClick
|
||||
? onClick
|
||||
: () => { }
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
className={`${classes.tag} ${isHighlighted ? classes.highlight : tagType === 'source' ? classes.source : tagType === 'auto' ? classes.auto : ''} ${isFetching ? classes.loading : ''}`}
|
||||
onTouchTap={(e) => {
|
||||
e.stopPropagation()
|
||||
onClickCallback(tagName)
|
||||
}}>
|
||||
<span>{tagName}</span>
|
||||
{!isFetching && showRemoveIcon && <ClearIcon
|
||||
className={classes.removeTagButton}
|
||||
onTouchTap={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemoveCallback(tagType, tagName)
|
||||
}}
|
||||
hoverColor='#FF5722'
|
||||
style={{ color: 'inherit', width: '1em', height: '1em' }}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Tag.propTypes = {
|
||||
tagName: React.PropTypes.string.isRequired,
|
||||
tagType: React.PropTypes.string.isRequired,
|
||||
onRemove: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
isHighlighted: React.PropTypes.bool,
|
||||
showRemoveIcon: React.PropTypes.bool
|
||||
}
|
||||
|
||||
export default Tag
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
.tag {
|
||||
margin: 4px;
|
||||
cursor: pointer;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
font-size: 12px;
|
||||
font-family: Roboto, sans-serif;
|
||||
color: rgb(0, 188, 212);
|
||||
padding: 2px;
|
||||
background-color: rgba(0, 188, 212, 0.08);
|
||||
transition: background-color 0.5s ease;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background-color: rgba(0, 188, 212, 0.2);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #fff176;
|
||||
color: rgb(158, 158, 158);
|
||||
}
|
||||
|
||||
.source {
|
||||
background-color: #DFFBD8;
|
||||
color: rgb(158, 158, 158);
|
||||
}
|
||||
|
||||
.auto {
|
||||
background-color: #F7E2E2;
|
||||
color: rgb(158, 158, 158);
|
||||
}
|
||||
|
||||
.tag > .removeTagButton {
|
||||
display: block;
|
||||
margin: 2px 1px 0 1px;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
background-color: #B2EBF2;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import Tag from './Tag'
|
||||
|
||||
export default Tag
|
||||
@ -0,0 +1,5 @@
|
||||
import Tag from './Tag'
|
||||
|
||||
export {
|
||||
Tag
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import TagsInput from './TagsInput'
|
||||
|
||||
export default TagsInput
|
||||
@ -0,0 +1,24 @@
|
||||
import React, { Component } from 'react'
|
||||
import HighlightedSpan from '../HighlightedSpan'
|
||||
|
||||
import classes from './UpdatedDateTimeLabel.scss'
|
||||
|
||||
const UpdatedDateTimeLabel = ({ meta, searchQuery, formatFunc, ...otherProps }) => {
|
||||
const WHEN_QUERY = /((^|\s)when:)((today)|(yesterday)|(thisweek)|(thismonth)|(thisyear))/im
|
||||
|
||||
const updatedDatetimeHighlighted = searchQuery ? WHEN_QUERY.test(searchQuery) : false
|
||||
const updatedDatetime = meta.updated_datetime
|
||||
const displayedUpdatedDateTime = updatedDatetime && formatFunc ? formatFunc(updatedDatetime) : updatedDatetime
|
||||
|
||||
return (
|
||||
<HighlightedSpan isHighlighted={updatedDatetimeHighlighted} {...otherProps}>{displayedUpdatedDateTime}</HighlightedSpan>
|
||||
)
|
||||
}
|
||||
|
||||
UpdatedDateTimeLabel.propTypes = {
|
||||
meta: React.PropTypes.object.isRequired,
|
||||
searchQuery: React.PropTypes.string.isRequired,
|
||||
formatFunc: React.PropTypes.func
|
||||
}
|
||||
|
||||
export default UpdatedDateTimeLabel
|
||||
@ -0,0 +1,3 @@
|
||||
import UpdatedDateTimeLabel from './UpdatedDateTimeLabel'
|
||||
|
||||
export default UpdatedDateTimeLabel
|
||||
@ -0,0 +1,33 @@
|
||||
import NotificationIndicator from './NotificationIndicator'
|
||||
import InfiniteScroll from './InfiniteScroll'
|
||||
import CodeEditor from './CodeEditor'
|
||||
import AmbarResponsiveLogo from './AmbarResponsiveLogo'
|
||||
import FullScreenLoader from './FullScreenLoader'
|
||||
import FullScreenPattern from './FullScreenPattern'
|
||||
import AuthPageTemplate from './AuthPageTemplate'
|
||||
import LoadingIndicator from './LoadingIndicator'
|
||||
import FileAvatar from './FileAvatar'
|
||||
import HighlightedSpan from './HighlightedSpan'
|
||||
import TagsInput from './TagsInput'
|
||||
import ClickableFilePath from './ClickableFilePath'
|
||||
import AuthorLabel from './AuthorLabel'
|
||||
import FileSizeLabel from './FileSizeLabel'
|
||||
import UpdatedDateTimeLabel from './UpdatedDateTimeLabel'
|
||||
|
||||
export {
|
||||
NotificationIndicator,
|
||||
InfiniteScroll,
|
||||
CodeEditor,
|
||||
AmbarResponsiveLogo,
|
||||
FullScreenLoader,
|
||||
FullScreenPattern,
|
||||
AuthPageTemplate,
|
||||
LoadingIndicator,
|
||||
FileAvatar,
|
||||
HighlightedSpan,
|
||||
TagsInput,
|
||||
ClickableFilePath,
|
||||
AuthorLabel,
|
||||
FileSizeLabel,
|
||||
UpdatedDateTimeLabel
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
import React, { Component } from 'react'
|
||||
import { titles } from 'utils/'
|
||||
|
||||
import { SearchResults, ImagePreview, SearchInput, SideMenu } from './components'
|
||||
import { InfiniteScroll } from 'components/BasicComponents'
|
||||
import UploadContainer from 'routes/SearchPage/containers/UploadModalContainer'
|
||||
import ImagePreviewContainer from 'routes/SearchPage/containers/ImagePreviewContainer'
|
||||
|
||||
import { cyan100, cyan300, cyan400 } from 'material-ui/styles/colors'
|
||||
import MoreHoriz from 'material-ui/svg-icons/navigation/more-horiz'
|
||||
import MediaQuery from 'react-responsive'
|
||||
import ArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
|
||||
import classes from './Search.scss'
|
||||
|
||||
const Desktop = ({ children }) => <MediaQuery query='(min-width: 1024px)' children={children} />
|
||||
|
||||
class Search extends Component {
|
||||
|
||||
timeoutId = null
|
||||
|
||||
componentDidMount() {
|
||||
const { setPageTitle, setAppHeader, loadTags, search, searchQuery, setQueryFromGetParam, setQuery, localization } = this.props
|
||||
|
||||
setPageTitle(localization.searchPage.pageTitle)
|
||||
setAppHeader({
|
||||
left: () => <Desktop>{localization.searchPage.pageTitle}</Desktop>,
|
||||
center: (state) => {
|
||||
return (
|
||||
<SearchInput
|
||||
setQuery={setQuery}
|
||||
query={state['searchPage'].searchQuery}
|
||||
search={search}
|
||||
localization={localization}
|
||||
/>)
|
||||
}
|
||||
})
|
||||
loadTags()
|
||||
setQueryFromGetParam()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { cleanUpSearchResult } = this.props
|
||||
cleanUpSearchResult()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
search,
|
||||
searchView,
|
||||
fetching,
|
||||
searchQuery,
|
||||
hasMore,
|
||||
scrolledDown,
|
||||
setScrolledDown,
|
||||
setPageTitle,
|
||||
currentPage,
|
||||
mode,
|
||||
toggleUploadModal,
|
||||
setAppHeader,
|
||||
performSearchByQuery,
|
||||
performSearchBySize,
|
||||
performSearchByWhen,
|
||||
performSearchByShow,
|
||||
performSearchByTag,
|
||||
setSearchResultView,
|
||||
allTags,
|
||||
localization
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%' }}>
|
||||
<Desktop>
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
width: '200px',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
boxShadow: '0 0 15px rgba(0, 0, 0, 0.4)',
|
||||
padding: '0'
|
||||
}}>
|
||||
<SideMenu
|
||||
performSearchByQuery={performSearchByQuery}
|
||||
performSearchBySize={performSearchBySize}
|
||||
performSearchByWhen={performSearchByWhen}
|
||||
performSearchByShow={performSearchByShow}
|
||||
performSearchByTag={performSearchByTag}
|
||||
toggleUploadModal={toggleUploadModal}
|
||||
setSearchResultView={setSearchResultView}
|
||||
searchView={searchView}
|
||||
allTags={allTags}
|
||||
localization={localization}
|
||||
/>
|
||||
</div>
|
||||
</Desktop>
|
||||
<MediaQuery query='(min-width: 1024px)'>
|
||||
{
|
||||
(matches) => {
|
||||
return (<div style={{ marginLeft: matches ? '200px' : '0', height: '100%', overflowY: 'auto', backgroundColor: 'rgba(0,0,0,0.05)' }}
|
||||
ref={(container) => { this.containerNode = container }}>
|
||||
<SearchResults searchView={searchView} />
|
||||
{this.containerNode && <InfiniteScroll
|
||||
anchorEl={this.containerNode}
|
||||
currentPage={currentPage}
|
||||
threshold={100}
|
||||
loadMore={(newPage) => {
|
||||
search(newPage, searchQuery)
|
||||
}}
|
||||
hasMore={hasMore}
|
||||
onScrollDown={(isFirstPage) => setScrolledDown(!isFirstPage)}
|
||||
/>}
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
</MediaQuery>
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', position: 'fixed', bottom: '10%', right: '30px', zIndex: '990' }}>
|
||||
<FloatingActionButton
|
||||
zDepth={4}
|
||||
onTouchTap={() => { this.containerNode.scrollTop = 0 }}
|
||||
className={scrolledDown ? '' : 'hiddenWithAnimation'}>
|
||||
<ArrowUpward />
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<UploadContainer />
|
||||
<ImagePreviewContainer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Search.propTypes = {
|
||||
searchView: React.PropTypes.string.isRequired,
|
||||
setPageTitle: React.PropTypes.func.isRequired,
|
||||
|
||||
setScrolledDown: React.PropTypes.func.isRequired,
|
||||
scrolledDown: React.PropTypes.bool.isRequired,
|
||||
|
||||
fetching: React.PropTypes.bool.isRequired,
|
||||
|
||||
currentPage: React.PropTypes.number.isRequired,
|
||||
hasMore: React.PropTypes.bool.isRequired,
|
||||
|
||||
searchQuery: React.PropTypes.string.isRequired,
|
||||
|
||||
loadTags: React.PropTypes.func.isRequired,
|
||||
allTags: React.PropTypes.array.isRequired,
|
||||
|
||||
toggleUploadModal: React.PropTypes.func.isRequired,
|
||||
|
||||
setQuery: React.PropTypes.func.isRequired,
|
||||
search: React.PropTypes.func.isRequired,
|
||||
performSearchByQuery: React.PropTypes.func.isRequired,
|
||||
performSearchBySize: React.PropTypes.func.isRequired,
|
||||
performSearchByWhen: React.PropTypes.func.isRequired,
|
||||
performSearchByShow: React.PropTypes.func.isRequired,
|
||||
performSearchByTag: React.PropTypes.func.isRequired,
|
||||
|
||||
setSearchResultView: React.PropTypes.func.isRequired,
|
||||
localization: React.PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default Search
|
||||
@ -0,0 +1,15 @@
|
||||
.header {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.filter {
|
||||
input[type="text"] {
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadFileBtn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Card, CardActions, CardHeader, CardText, CardTitle } from 'material-ui/Card'
|
||||
import Paper from 'material-ui/Paper'
|
||||
|
||||
import classes from './EmptyCard.scss'
|
||||
|
||||
const EmptyCard = (props) => {
|
||||
const {title, textElement} = props
|
||||
|
||||
return (
|
||||
<Paper zDepth={1} className={classes.emptyCard}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
className={classes.emptyCardTitle}
|
||||
title={<span className={classes.emptyCardHeaderTitle}>{title}</span>}
|
||||
/>
|
||||
<CardText>
|
||||
{textElement}
|
||||
</CardText>
|
||||
</Card>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
EmptyCard.propTypes = {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
textElement: React.PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default EmptyCard
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
.emptyCard {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.emptyCardHeaderTitle {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.emptyCardTitle {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import EmptyCard from './EmptyCard'
|
||||
|
||||
export default EmptyCard
|
||||
@ -0,0 +1,37 @@
|
||||
import React, { Component } from 'react'
|
||||
import HintCard from '../HintCard'
|
||||
import classes from './EmptySearchResultsCard.scss'
|
||||
|
||||
class EmptySearchResultsCard extends Component {
|
||||
render() {
|
||||
const { searchQuery, performSearchByQuery, localization } = this.props
|
||||
|
||||
if (searchQuery != '') {
|
||||
return (<div className='pageContainer'>
|
||||
<HintCard
|
||||
title={localization.searchPage.nothingFoundLabel}
|
||||
description={<span><i>{searchQuery}</i> - {localization.searchPage.nothingFoundDescriptionLabel}</span>}
|
||||
performSearchByQuery={performSearchByQuery}
|
||||
localization={localization}
|
||||
/>
|
||||
</div>)
|
||||
}
|
||||
|
||||
return (<div className='pageContainer'>
|
||||
<HintCard
|
||||
title={localization.searchPage.searchTipsLabel}
|
||||
description={<span>{localization.searchPage.searchTipsDescriptionLabel}</span>}
|
||||
performSearchByQuery={performSearchByQuery}
|
||||
localization={localization}
|
||||
/>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
EmptySearchResultsCard.propTypes = {
|
||||
searchQuery: React.PropTypes.string.isRequired,
|
||||
performSearchByQuery: React.PropTypes.func.isRequired,
|
||||
localization: React.PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default EmptySearchResultsCard
|
||||
@ -0,0 +1,3 @@
|
||||
import EmptySearchResultsCard from './EmptySearchResultsCard'
|
||||
|
||||
export default EmptySearchResultsCard
|
||||
@ -0,0 +1,94 @@
|
||||
import React, { Component } from 'react'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import EmptyCard from '../EmptyCard'
|
||||
import classes from './HintCard.scss'
|
||||
|
||||
const HintCard = (props) => {
|
||||
const { title, description, performSearchByQuery, localization } = props
|
||||
|
||||
const hintText = (<div><p>{localization.searchPage.refineTipsLabel}</p>
|
||||
<ul>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('*') }}>
|
||||
*
|
||||
</span> - {localization.searchPage.allFilesQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('John Smith') }}>
|
||||
John Smith
|
||||
</span> - {localization.searchPage.simpleQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('"John Smith"') }}>
|
||||
"John Smith"
|
||||
</span> - {localization.searchPage.pharseQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('"John Smith"~10') }}>
|
||||
"John Smith"~10
|
||||
</span> - {localization.searchPage.pharseQueryWithDistanceLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('John~3') }}>
|
||||
John~3
|
||||
</span> - {localization.searchPage.fuzzyQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('filename:*.txt') }}>
|
||||
filename:*.txt
|
||||
</span> - {localization.searchPage.filenameQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('size>1M') }}>
|
||||
size>1M
|
||||
</span> - {localization.searchPage.sizeQueryLabel}
|
||||
</li>
|
||||
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('when:today') }}>
|
||||
when:today
|
||||
</span> - {localization.searchPage.whenQueryLabel}
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('author:*') }}>
|
||||
author:*
|
||||
</span> - {localization.searchPage.authorQueryLabel}
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('tags:ocr,ui-upload') }}>
|
||||
tags:ocr,ui-upload
|
||||
</span> - {localization.searchPage.tagsQueryLabel}
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('entities:"hello@ambar.cloud"') }}>
|
||||
entities:"hello@ambar.cloud"
|
||||
</span> - {localization.searchPage.entitiesQueryLabel}
|
||||
</li>
|
||||
<li>
|
||||
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('show:removed') }}>
|
||||
show:removed
|
||||
</span> - {localization.searchPage.removedQueryLabel}
|
||||
</li>
|
||||
</ul>
|
||||
</div>)
|
||||
|
||||
const emailText = (<p>
|
||||
{localization.searchPage.haveQuestionsLabel} <a className={classes.link} href='mailto:hello@ambar.cloud'>{localization.searchPage.dropMessageLabel}</a>
|
||||
</p>)
|
||||
|
||||
const textElement = (<div>
|
||||
<p style={{ marginTop: 0 }}>{description}</p>
|
||||
{hintText}
|
||||
{emailText}
|
||||
</div>)
|
||||
|
||||
return (
|
||||
<EmptyCard
|
||||
title={title}
|
||||
textElement={textElement} />
|
||||
)
|
||||
}
|
||||
|
||||
HintCard.propTypes = {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.object.isRequired,
|
||||
performSearchByQuery: React.PropTypes.func.isRequired,
|
||||
localization: React.PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default HintCard
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
.link, .link:hover,.link:visited {
|
||||
color: #00acc1;
|
||||
text-decoration: underline;
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
|
||||
span.clickableSpan {
|
||||
color: #444477;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span.clickableSpan:hover {
|
||||
border-radius: 4px;
|
||||
background-color: #B2EBF2;
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
|
||||
span.clickableSpan:active {
|
||||
border-radius: 4px;
|
||||
background-color: #80DEEA;
|
||||
cursor: pointer; cursor: hand;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import HintCard from './HintCard'
|
||||
|
||||
export default HintCard
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue