107 files changed
+15274
-5319
No files matched your search
@@ -1,4 +1,17 @@
|
||||
{
|
||||
"presets": ["es2015", "stage-0"],
|
||||
"plugins": ["transform-react-jsx"]
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "8.5.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"transform-react-jsx",
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: docker
|
||||
commands:
|
||||
- docker build -t tmshv/hudozka:latest -t tmshv/hudozka:build-${DRONE_BUILD_NUMBER} .
|
||||
- docker save -o hudozka-docker_container${DRONE_BUILD_NUMBER}.tar tmshv/hudozka:build-${DRONE_BUILD_NUMBER}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
scp:
|
||||
image: appleboy/drone-scp
|
||||
host: art.shlisselburg.org
|
||||
username: master
|
||||
target: ~/Hudozka
|
||||
source: hudozka-docker_container${DRONE_BUILD_NUMBER}.tar
|
||||
secrets: [scp_key]
|
||||
when:
|
||||
branch: master
|
||||
@@ -1 +0,0 @@
|
||||
6.9.1
|
||||
@@ -1,12 +1,14 @@
|
||||
# Build
|
||||
FROM node:8 as build
|
||||
FROM node:10 as build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json gulpfile.babel.js .babelrc ./
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i
|
||||
|
||||
COPY webpack.config.js gulpfile.js .babelrc ./
|
||||
COPY robots.txt ./
|
||||
COPY src ./src
|
||||
RUN npm run gulp deploy
|
||||
RUN npm run gulp
|
||||
RUN npm run build-app
|
||||
RUN npm run out
|
||||
|
||||
|
||||
@@ -14,7 +16,7 @@ RUN npm run out
|
||||
FROM node:8-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json gulpfile.babel.js .babelrc ./
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i --production
|
||||
|
||||
COPY --from=build /app/out ./src
|
||||
@@ -1,30 +1,27 @@
|
||||
# [ART.SHLISSELBURG.ORG](https://art.shlisselburg.org)
|
||||
Art school of **Shlisselburg**
|
||||
|
||||
Site of Art school of Shlisselburg
|
||||
|
||||
## Usage
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
$ docker build -t tmshv/hudozka .
|
||||
```
|
||||
|
||||
### How to run
|
||||
|
||||
```bash
|
||||
$ <ENV> npm start
|
||||
#### Add .env file
|
||||
|
||||
```
|
||||
MONGO_HUDOZKA_PASSWORD=<strong_password>
|
||||
HUDOZKA_DB_URI=mongodb://hudozka:strong_password@mongo:27017/hudozka?authSource=admin
|
||||
```
|
||||
|
||||
### Environments variables
|
||||
|
||||
#### PORT
|
||||
|
||||
TCP Port for app
|
||||
|
||||
#### Run
|
||||
```bash
|
||||
PORT=8080 npm start
|
||||
```
|
||||
|
||||
#### PRIVATE
|
||||
|
||||
Path to JSON file with secret keys
|
||||
|
||||
```bash
|
||||
PRIVATE=private.json npm start
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
### How to sync data base
|
||||
@@ -39,23 +36,11 @@ PWD=<mongo_password>
|
||||
mongodump -d ${DB} --gzip --archive | mongorestore -h ${HOST} -u ${USER} -p ${PWD} -d ${DB} --gzip --archive
|
||||
```
|
||||
|
||||
## Build client
|
||||
|
||||
```bash
|
||||
$ NODE_ENV=production gulp deploy
|
||||
```
|
||||
|
||||
For developer purposes:
|
||||
|
||||
```bash
|
||||
$ gulp deploy
|
||||
```
|
||||
|
||||
### Based on:
|
||||
- [NodeJS](http://nodejs.org) 6.0
|
||||
## Based on:
|
||||
- [NodeJS](http://nodejs.org) 10.0
|
||||
- [koa.js](http://koajs.com) 2.0
|
||||
- [angular.js](https://angularjs.org) 1.5
|
||||
- [MongoDB](http://mongodb.org) 3.2
|
||||
- [React](https://react.org) 16.3
|
||||
- [MongoDB](http://mongodb.org) 3.6
|
||||
|
||||
## Contributors
|
||||
- Roman Timashev ([roman@tmshv.ru](mailto:roman@tmshv.ru))
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Deploying art.shburg.org"
|
||||
|
||||
server="art.shlisselburg.org"
|
||||
tarfile="build.tar.gz"
|
||||
tarpath=$TMPDIR${tarfile}
|
||||
destpath="~/www/art.shburg.org"
|
||||
|
||||
echo "Building..."
|
||||
npm run out
|
||||
NODE_ENV=production npm run gulp deploy
|
||||
|
||||
echo "Packing $tarpath"
|
||||
env GZIP=-9 tar -czf ${tarpath} \
|
||||
--disable-copyfile \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.idea \
|
||||
--exclude=.DS_Store \
|
||||
--exclude=src \
|
||||
--exclude=deploy.sh \
|
||||
--exclude=install.sh \
|
||||
.
|
||||
|
||||
echo "Uploading..."
|
||||
ssh hoster@${server} "rm -rf $destpath/public/*"
|
||||
scp ${tarpath} hoster@${server}:${destpath}
|
||||
|
||||
echo "Installing..."
|
||||
ssh hoster@${server} "bash -s" -- < ./install.sh ${destpath} ${tarfile}
|
||||
|
||||
echo "Clear..."
|
||||
rm ${tarpath}
|
||||
|
||||
echo "Done"
|
||||
+19
-1
@@ -1,10 +1,28 @@
|
||||
version: '3'
|
||||
volumes:
|
||||
data:
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:3.6
|
||||
restart: always
|
||||
volumes:
|
||||
- data:/data/db
|
||||
ports:
|
||||
- "1817:27017"
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=hudozka
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_HUDOZKA_PASSWORD}
|
||||
|
||||
app:
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongo
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Hudozka.Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
links:
|
||||
- mongo
|
||||
ports:
|
||||
- "1880:3000"
|
||||
environment:
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import gulp from 'gulp'
|
||||
import postcss from 'gulp-postcss'
|
||||
import sass from 'gulp-sass'
|
||||
import imageMin from 'gulp-imagemin'
|
||||
import uglify from 'gulp-uglify'
|
||||
import concat from 'gulp-concat'
|
||||
import webpack from 'webpack-stream'
|
||||
import named from 'vinyl-named'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import cssnano from 'cssnano'
|
||||
|
||||
let env = process.env['NODE_ENV'] || 'production'
|
||||
let isProduction = env === 'production'
|
||||
|
||||
gulp.task('compile 3rdparty', () => {
|
||||
const files = [
|
||||
'./node_modules/spin.js/spin.js',
|
||||
|
||||
'./node_modules/babel-polyfill/dist/polyfill.min.js'
|
||||
]
|
||||
|
||||
return gulp.src(files)
|
||||
.pipe(concat('libs.js'))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('style', () => {
|
||||
const processors = [
|
||||
autoprefixer({
|
||||
browsers: ['last 1 version']
|
||||
}),
|
||||
cssnano({
|
||||
zindex: false
|
||||
})
|
||||
]
|
||||
|
||||
return gulp.src('./src/style/style.scss')
|
||||
.pipe(sass())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(concat('style.css'))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy fonts', () => {
|
||||
return gulp.src('./src/assets/fonts/*/*')
|
||||
.pipe(gulp.dest('./public/fonts'))
|
||||
})
|
||||
|
||||
gulp.task('copy graphics', () => {
|
||||
return gulp.src('./src/assets/graphics/*')
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy 3rdparty', () => {
|
||||
return gulp.src('./src/3rdparty/*')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy robots.txt', () => {
|
||||
return gulp.src('./robots.txt')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy views', () => {
|
||||
return gulp.src('./src/views/**')
|
||||
.pipe(gulp.dest('./out/views'))
|
||||
})
|
||||
|
||||
gulp.task('compile', () => {
|
||||
const doWatch = !isProduction
|
||||
const doSourceMaps = !isProduction
|
||||
|
||||
return gulp.src([
|
||||
'./src/public/app.js'
|
||||
])
|
||||
.pipe(named())
|
||||
.pipe(webpack({
|
||||
// devtool: doSourceMaps ? 'inline-source-map' : null,
|
||||
// watch: doWatch,
|
||||
output: {
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{test: /\.js$/, loader: 'babel'},
|
||||
// {test: /\.html$/, loader: 'raw!html-minify'}
|
||||
]
|
||||
},
|
||||
// 'html-minify-loader': {
|
||||
// empty: true,
|
||||
// cdata: true,
|
||||
// comments: false,
|
||||
// dom: {
|
||||
// lowerCaseAttributeNames: false
|
||||
// }
|
||||
// }
|
||||
}))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('imagemin', () => {
|
||||
return gulp.src('./src/assets/graphics/**/*')
|
||||
.pipe(imageMin())
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('production', () => {
|
||||
isProduction = true
|
||||
})
|
||||
|
||||
gulp.task('default', ['style', 'compile'])
|
||||
gulp.task('copy', ['copy views', 'copy fonts', 'copy graphics', 'copy 3rdparty', 'copy robots.txt'])
|
||||
gulp.task('deploy', ['default', 'copy', 'compile 3rdparty', 'imagemin'])
|
||||
gulp.task('compile production', ['production', 'compile'])
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
const gulp = require('gulp')
|
||||
const postcss = require('gulp-postcss')
|
||||
const sass = require('gulp-sass')
|
||||
const imageMin = require('gulp-imagemin')
|
||||
const concat = require('gulp-concat')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssnano = require('cssnano')
|
||||
|
||||
gulp.task('style', () => {
|
||||
const processors = [
|
||||
autoprefixer({
|
||||
browsers: ['last 1 version']
|
||||
}),
|
||||
cssnano({
|
||||
zindex: false
|
||||
})
|
||||
]
|
||||
|
||||
return gulp.src('./src/style/style.scss')
|
||||
.pipe(sass())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(concat('style.css'))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy fonts', () => {
|
||||
return gulp.src('./src/assets/fonts/*/*')
|
||||
.pipe(gulp.dest('./public/fonts'))
|
||||
})
|
||||
|
||||
gulp.task('copy graphics', () => {
|
||||
return gulp.src('./src/assets/graphics/*')
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy 3rdparty', () => {
|
||||
return gulp.src('./src/3rdparty/*')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy robots.txt', () => {
|
||||
return gulp.src('./robots.txt')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy views', () => {
|
||||
return gulp.src('./src/views/**')
|
||||
.pipe(gulp.dest('./out/views'))
|
||||
})
|
||||
|
||||
gulp.task('imagemin', () => {
|
||||
return gulp.src('./src/assets/graphics/**/*')
|
||||
.pipe(imageMin())
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy', gulp.series('copy views', 'copy fonts', 'copy graphics', 'copy robots.txt', 'copy 3rdparty'))
|
||||
gulp.task('default', gulp.series('style', 'copy', 'imagemin'))
|
||||
@@ -1,4 +0,0 @@
|
||||
rsa-key-size = 4096
|
||||
email = roman@tmshv.ru
|
||||
domains = art.shlisselburg.org
|
||||
webroot-path = /home/hoster/www/art.shburg.org/public
|
||||
Generated
+11789
-4730
File diff suppressed because it is too large.
Load diff
+43
-16
@@ -6,6 +6,8 @@
|
||||
"main": "src/bin/www.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"build-edit": "webpack -w --env.NODE_ENV=production --env.TARGET=edit",
|
||||
"build-app": "webpack --env.NODE_ENV=production --env.TARGET=app",
|
||||
"out": "babel src/ -d out/",
|
||||
"gulp": "gulp",
|
||||
"test": "mocha --compilers js:babel-register",
|
||||
@@ -18,16 +20,22 @@
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mikeljames/draft-js-drag-n-drop-upload-plugin": "^2.0.1",
|
||||
"async-file": "^2.0.2",
|
||||
"babel-core": "^6.7.4",
|
||||
"babel-polyfill": "^6.7.4",
|
||||
"babel-preset-es2015": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"classnames": "^2.2.5",
|
||||
"co": "^4.4.0",
|
||||
"draft-js": "^0.10.5",
|
||||
"draft-js-alignment-plugin": "^2.0.3",
|
||||
"draft-js-drag-n-drop-plugin": "^2.0.3",
|
||||
"draft-js-focus-plugin": "^2.0.4",
|
||||
"draft-js-image-plugin": "^2.0.5",
|
||||
"draft-js-plugins-editor": "^2.0.8",
|
||||
"draft-js-resizeable-plugin": "^2.0.6",
|
||||
"ejson": "^2.1.2",
|
||||
"handlebars": "^4.0.5",
|
||||
"immutable": "^3.8.2",
|
||||
"instagram-node": "^0.5.1",
|
||||
"koa": "^2.0.0-alpha.3",
|
||||
"koa": "^2.5.1",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-compose": "^3.1.0",
|
||||
"koa-conditional-get": "^1.0.2",
|
||||
@@ -37,46 +45,65 @@
|
||||
"koa-helmet": "^1.0.0",
|
||||
"koa-logger": "^2.0.0",
|
||||
"koa-mount": "^2.0.0",
|
||||
"koa-query": "0.0.2",
|
||||
"koa-route": "^3.1.0",
|
||||
"koa-session": "^4.0.0",
|
||||
"koa-static": "^3.0.0",
|
||||
"moment": "^2.18.1",
|
||||
"markdown": "^0.5.0",
|
||||
"moment": "^2.22.2",
|
||||
"mongodb": "^2.1.14",
|
||||
"mz": "^2.4.0",
|
||||
"react": "^15.3.2",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-redux": "^4.4.5",
|
||||
"redux": "^3.6.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-redux": "^4.4.8",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"request-promise": "^2.0.1",
|
||||
"sitemap": "^1.5.0",
|
||||
"thunkify": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0-beta.47",
|
||||
"@babel/core": "^7.0.0-beta.47",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.47",
|
||||
"@babel/polyfill": "^7.0.0-beta.47",
|
||||
"@babel/preset-env": "^7.0.0-beta.47",
|
||||
"@babel/register": "^7.0.0-beta.47",
|
||||
"autoprefixer": "^6.3.6",
|
||||
"babel-cli": "^6.6.5",
|
||||
"babel-loader": "^6.2.2",
|
||||
"babel-loader": "^8.0.0-beta.3",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"css-loader": "^0.28.9",
|
||||
"cssnano": "^3.4.0",
|
||||
"eslint": "^3.9.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"gulp": "^3.8.10",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-imagemin": "~2.4.0",
|
||||
"gulp-postcss": "^6.0.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-uglify": "~1.5.0",
|
||||
"html-minify-loader": "~1.1.0",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"istanbul": "~0.4.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"mocha": "~2.4.5",
|
||||
"raw-loader": "~0.5.1",
|
||||
"should": "^8.2.2",
|
||||
"style-loader": "^0.20.2",
|
||||
"vinyl-named": "~1.1.0",
|
||||
"webpack-stream": "~3.1.0"
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-merge": "^4.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
const React = require('react')
|
||||
|
||||
const mailto = email => `mailto:${email}`
|
||||
const currentYear = () => (new Date()).getFullYear()
|
||||
|
||||
const Footer = ({children, address, telephone, email, showAuthor}) => (
|
||||
<footer>
|
||||
@@ -16,16 +17,28 @@ const Footer = ({children, address, telephone, email, showAuthor}) => (
|
||||
</div>
|
||||
|
||||
<div className="copyright">
|
||||
<span>© 2012—2017 Шлиссельбургская детская художественная школа</span>
|
||||
<Copyright
|
||||
yearStart={2012}
|
||||
yearEnd={currentYear()}
|
||||
/>
|
||||
{!showAuthor ? null : (
|
||||
<span>Разработка и поддержка сайта —
|
||||
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
|
||||
www.tmshv.ru
|
||||
</a>
|
||||
</span>
|
||||
<Author/>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
|
||||
const Author = () => (
|
||||
<span>
|
||||
Разработка и поддержка сайта —
|
||||
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
|
||||
www.tmshv.ru
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
|
||||
const Copyright = ({yearStart, yearEnd}) => (
|
||||
<span>© {yearStart}—{yearEnd} Шлиссельбургская детская художественная школа</span>
|
||||
)
|
||||
|
||||
module.exports = Footer
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
import path from 'path'
|
||||
|
||||
export const port = process.env['PORT'] || 3000
|
||||
export const dbUri = process.env['DB_URI'] || 'mongodb://localhost:27017/hudozka'
|
||||
export const dbUri = process.env['DB_URI'] || 'mongodb://mongo/hudozka'
|
||||
|
||||
export const name = 'Hudozka'
|
||||
export const host = 'art.shlisselburg.org'
|
||||
@@ -12,6 +12,7 @@ export const index = path.join(__dirname, 'templates/main.html')
|
||||
export const sitemapCacheTime = 600000// 600 sec - cache purge period
|
||||
|
||||
export const viewMain = path.join(__dirname, 'views/main.hbs')
|
||||
export const viewEdit = path.join(__dirname, 'views/edit.hbs')
|
||||
export const view404 = path.join(__dirname, 'views/404.hbs')
|
||||
|
||||
export const sessionConfig = {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// import 'babel-core/register'
|
||||
// import 'babel-polyfill'
|
||||
|
||||
import {render} from 'react-dom'
|
||||
import getEditApp from './edit'
|
||||
import TextToken from './edit/features/Marker/lib/marker/TextToken'
|
||||
import {Marker} from './edit/features/Marker/lib/marker/Marker'
|
||||
import SplitToken from './edit/features/Marker/lib/marker/SplitToken'
|
||||
import YoutubeToken from './edit/features/Marker/lib/marker/YoutubeToken'
|
||||
import {TokenFactory} from './edit/features/Marker/lib/marker/TokenFactory'
|
||||
import React from 'react'
|
||||
import UrlToken from './edit/features/Marker/lib/marker/UrlToken'
|
||||
|
||||
function getMarker() {
|
||||
return (new Marker())
|
||||
.addTokenFactory(new TokenFactory({token: SplitToken}))
|
||||
.addTokenFactory(new TokenFactory({
|
||||
token: YoutubeToken,
|
||||
component: ({url}) => (
|
||||
<div
|
||||
className='MarkerWidgetYoutube'
|
||||
>
|
||||
<a href={url}>
|
||||
{url}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
// component: ({url}) => (
|
||||
// <div
|
||||
// className='MarkerWidgetYoutube'
|
||||
// >
|
||||
// <div className='kazimir__video'>
|
||||
// <iframe
|
||||
// src={url}
|
||||
// frameBorder='0'
|
||||
// allowFullScreen
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
}))
|
||||
// .addTokenFactory(new TokenFactory({token: InstagramToken}))
|
||||
// .addTokenFactory(BuildTokenFactory(DocumentUrlToken, build=read_document))
|
||||
// .addTokenFactory(new TokenFactory({token: ImageToken}))
|
||||
// .addTokenFactory(BuildTokenFactory(DocumentToken, build=build_document))
|
||||
// .addTokenFactory(BuildTokenFactory(CSVToken, build=read_csv))
|
||||
// .addTokenFactory(new TokenFactory({token: FileToken}))
|
||||
.addTokenFactory(new TokenFactory({
|
||||
token: TextToken,
|
||||
component: ({html}) => (
|
||||
<div
|
||||
className='MarkerWidgetHtml'
|
||||
dangerouslySetInnerHTML={{__html: html}}
|
||||
/>
|
||||
)
|
||||
}), {
|
||||
blank: true,
|
||||
})
|
||||
// .add_tree_middleware(fix_links_quotes)
|
||||
|
||||
// sample_tree = await marker.create_tree(sample_text)
|
||||
}
|
||||
|
||||
function getData() {
|
||||
return `
|
||||
# Edit
|
||||
https://www.youtube.com/watch?v=pxtCffGya9g
|
||||
|
||||
Text row
|
||||
`
|
||||
// return [
|
||||
//
|
||||
// 'bla-bla',
|
||||
// '### pop-pop',
|
||||
// 'uiu',
|
||||
// 'Url: [ya](http://ya.ru)',
|
||||
// ]
|
||||
}
|
||||
|
||||
function main() {
|
||||
const mount = document.querySelector('#app')
|
||||
const marker = getMarker()
|
||||
const data = getData()
|
||||
const app = getEditApp(marker, data)
|
||||
|
||||
render(app, mount)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function effects() {
|
||||
return []
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import React, {Component} from 'react'
|
||||
import Marker from '../Marker'
|
||||
import './App.less'
|
||||
import {MarkerWidgetHtml, MarkerWidgetText, MarkerWidgetUrl} from '../Marker/MarkerWidget'
|
||||
import ContentEditable from '../Marker/ContentEditable'
|
||||
import Editor from '../Editor/'
|
||||
|
||||
// import Map from '../Map'
|
||||
// import Body from '../Body'
|
||||
// import AppWrapper from './AppWrapper'
|
||||
//
|
||||
// import {bindActionCreators} from 'redux'
|
||||
// import {connect} from 'react-redux'
|
||||
// import * as actions from './duck'
|
||||
//
|
||||
// import {routerActions} from 'react-router-redux'
|
||||
// import {title as defaultTitle} from '../../../config'
|
||||
// import {getVariable} from '../../../lib/style'
|
||||
// import {debounce} from '../../../lib/fn'
|
||||
// const hit = debounce(50, path => ym('hit', path))
|
||||
//
|
||||
// const projectPath = project => `/projects/${project.name}`
|
||||
// const frameSize = isVertical => [window.innerWidth, window.innerHeight]
|
||||
// const frameSizeMultiplier = () => getVariable('--content-width-state-normal')
|
||||
// const landscapeProjectBounds = (width, height, ratio=frameSizeMultiplier()) => [
|
||||
// [0, 0],
|
||||
// [width * ratio, 0],
|
||||
// ]
|
||||
// const portraitProjectBounds = (width, height, ratio=frameSizeMultiplier()) => [
|
||||
// [0, 0],
|
||||
// [0, width * ratio],
|
||||
// ]
|
||||
//
|
||||
// function mapStateToProps(state) {
|
||||
// const {fullPage, isVertical, halfPageAvailable} = state.app.page
|
||||
//
|
||||
// return {
|
||||
// projects: state.app.projects.items,
|
||||
// activeProject: state.app.projects.activeProject,
|
||||
// fullPage: halfPageAvailable
|
||||
// ? fullPage
|
||||
// : true,
|
||||
// showFullPageToggle: halfPageAvailable,
|
||||
// isVertical,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function mapDispatchToProps(dispatch) {
|
||||
// return bindActionCreators({
|
||||
// ...actions,
|
||||
// push: routerActions.push
|
||||
// }, dispatch)
|
||||
// }
|
||||
|
||||
// async function build_document(data) {
|
||||
// // text = data['caption'] if data['caption'] else data['file']
|
||||
// // # document = await self.build()
|
||||
// document = {
|
||||
// 'url': data['file'],
|
||||
// 'image_url': data['file'],
|
||||
// 'file_url': data['file'],
|
||||
// 'title': text,
|
||||
// 'file_size': '0 KB',
|
||||
// 'file_format': 'PDF',
|
||||
// }
|
||||
// return document
|
||||
// }
|
||||
|
||||
// async function read_csv(data) {
|
||||
// return '\n'.join([
|
||||
// 'N,Name',
|
||||
// `0,${data}`,
|
||||
// '1,Pop',
|
||||
// '2,Top',
|
||||
// ])
|
||||
// }
|
||||
|
||||
// async function read_document(data) {
|
||||
// const doc = await Document.find_one({'id': data})
|
||||
// return {
|
||||
// 'url': doc['url'],
|
||||
// 'image_url': doc['url'],
|
||||
// 'file_url': doc['url'],
|
||||
// 'title': doc['title'],
|
||||
// 'file_size': '0 KB',
|
||||
// 'file_format': 'PDF',
|
||||
// }
|
||||
// }
|
||||
|
||||
export default class App extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.onMarkerUpdate = this.onMarkerUpdate.bind(this)
|
||||
|
||||
this.state = {
|
||||
tokens: [],
|
||||
}
|
||||
}
|
||||
|
||||
onMarkerUpdate(tokens) {
|
||||
// console.log('Update')
|
||||
// console.log(tokens)
|
||||
|
||||
this.setState({tokens})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {data, marker} = this.props
|
||||
|
||||
marker.evaluate(data)
|
||||
.then(tokens => {
|
||||
this.setState({
|
||||
tokens,
|
||||
})
|
||||
})
|
||||
|
||||
// this.setState({
|
||||
// data: this
|
||||
// .getData()
|
||||
// .reduce((acc, x) => [...acc, ...x], []),
|
||||
// })
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<div className={'App-Body'}>
|
||||
<Editor/>
|
||||
{/* <Marker */}
|
||||
{/* data={this.state.tokens} */}
|
||||
{/* marker={this.props.marker} */}
|
||||
{/* onUpdate={this.onMarkerUpdate} */}
|
||||
{/* /> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// export default connect(mapStateToProps, mapDispatchToProps)(App)
|
||||
@@ -0,0 +1,9 @@
|
||||
.App {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
&-Body {
|
||||
padding-top: 10px;
|
||||
width: 800px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
.AppWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start; /* align items in Main Axis */
|
||||
align-items: stretch; /* align items in Cross Axis */
|
||||
align-content: stretch; /* Extra space in Cross Axis */
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.AppWrapper footer {
|
||||
background-color: rgba(34, 34, 34, 0.95);
|
||||
color: #ebebeb;
|
||||
|
||||
padding: 1em 0.5em 0.5em;
|
||||
font-weight: 100;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, {Component} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
|
||||
import './AppWrapper.css'
|
||||
import './AppWrapperHeader.css'
|
||||
import './AppWrapperBody.css'
|
||||
import './AppWrapperContent.css'
|
||||
import './Icon.css'
|
||||
|
||||
const Icon = ({children: iconName}) => (
|
||||
<span className={`Icon ${iconName}`}/>
|
||||
)
|
||||
|
||||
class Toggle extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
const {value} = this.state
|
||||
const {onClick} = this.props
|
||||
|
||||
this.setState({
|
||||
value: !value,
|
||||
})
|
||||
onClick(!value)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value} = this.state
|
||||
const icon = value
|
||||
? 'IconFullScreenOn'
|
||||
: 'IconFullScreenOff'
|
||||
|
||||
return (
|
||||
<button className="InvisibleButton" onClick={this.onClick}>
|
||||
<Icon>{icon}</Icon>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Head = ({title, onToggleFullPage, showFullPageToggle, showControls = false}) => (
|
||||
<header className="AppWrapperHeader">
|
||||
<div>
|
||||
</div>
|
||||
|
||||
<div className="AppWrapperHeader-main">
|
||||
<h1>{title}</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{!showControls ? null : (
|
||||
<div className="controls">
|
||||
{!showFullPageToggle ? null: (
|
||||
<Toggle onClick={onToggleFullPage}/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
const Body = ({children}) => (
|
||||
<div className="AppWrapperBody">
|
||||
<div className="AppWrapperContent">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const AppWrapper = ({title, children, showControls, showFullPageToggle, onToggleFullPage}) => (
|
||||
<div className="AppWrapper">
|
||||
<Head
|
||||
title={title}
|
||||
showControls={showControls}
|
||||
onToggleFullPage={onToggleFullPage}
|
||||
showFullPageToggle={showFullPageToggle}
|
||||
/>
|
||||
|
||||
<Body>{children}</Body>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default AppWrapper
|
||||
@@ -0,0 +1,7 @@
|
||||
.AppWrapperBody {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.AppWrapperContent {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
|
||||
position: relative;
|
||||
|
||||
/* Needed for when the area gets squished too far and there is content
|
||||
that can't be displayed */
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
.AppWrapperHeader {
|
||||
background-color: rgba(34, 34, 34, 0.95);
|
||||
color: #ebebeb;
|
||||
|
||||
/*background-color: rgba(255, 255, 255, 0.95);*/
|
||||
/*color: #222;*/
|
||||
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||
|
||||
pointer-events: auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.AppWrapperHeader-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.AppWrapperHeader h1 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: .5em 0 .4em;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.controls a{
|
||||
color: #bbb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.controls a:hover{
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.Icon {
|
||||
display: inline-block;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
/*background: grey;*/
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.IconFullScreenOn {
|
||||
background-image: url('/icon-fullscreen-on.svg');
|
||||
}
|
||||
|
||||
.IconFullScreenOff {
|
||||
background-image: url('/icon-fullscreen-off.svg');
|
||||
}
|
||||
|
||||
.InvisibleButton {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.InvisibleButton {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*.InvisibleButton:focus {*/
|
||||
/*border: 2px solid #2f9fff;*/
|
||||
/*}*/
|
||||
@@ -0,0 +1,107 @@
|
||||
import {combineReducers} from 'redux'
|
||||
import api from '../../../api'
|
||||
import {pack} from '../../../lib/fn'
|
||||
import {isMobile, isLandscape} from '../../../lib/device'
|
||||
|
||||
const FETCH_PROJECTS = 'arch/app/FETCH_PROJECTS'
|
||||
const FOCUS_PROJECT = 'arch/app/FOCUS_PROJECT'
|
||||
const TOGGLE_FULL_PAGE = 'arch/app/TOGGLE_FULL_PAGE'
|
||||
const CHANGE_PAGE_ORIENTATION = 'arch/app/CHANGE_PAGE_ORIENTATION'
|
||||
|
||||
export const PAGE_ORIENTATION_LANDSCAPE = 'landscape'
|
||||
export const PAGE_ORIENTATION_PORTRAIT = 'portrait'
|
||||
|
||||
export function fetchProjects() {
|
||||
return {
|
||||
type: FETCH_PROJECTS,
|
||||
async: api.projects.fetch()
|
||||
.then(pack('data'))
|
||||
}
|
||||
}
|
||||
|
||||
export function focusProject(projectName) {
|
||||
return {
|
||||
type: FOCUS_PROJECT,
|
||||
projectName
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleFullPage() {
|
||||
return {
|
||||
type: TOGGLE_FULL_PAGE,
|
||||
}
|
||||
}
|
||||
|
||||
export function changePageOrientation(orientation) {
|
||||
return {
|
||||
type: CHANGE_PAGE_ORIENTATION,
|
||||
orientation,
|
||||
}
|
||||
}
|
||||
|
||||
const init = {
|
||||
items: [],
|
||||
total: 0,
|
||||
activeProject: null,
|
||||
}
|
||||
|
||||
const initPage = {
|
||||
fullPage: false,
|
||||
halfPageAvailable: !isMobile(),
|
||||
isVertical: !isLandscape(),
|
||||
}
|
||||
|
||||
function projects(state = init, action) {
|
||||
switch (action.type) {
|
||||
case FETCH_PROJECTS: {
|
||||
const {items, total} = action.data
|
||||
return {...state, items, total}
|
||||
}
|
||||
|
||||
case FOCUS_PROJECT: {
|
||||
const projectName = action.projectName
|
||||
const activeProject = state.items.find(p => p.name === projectName)
|
||||
|
||||
return {...state, activeProject}
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function page(state = initPage, action) {
|
||||
switch (action.type) {
|
||||
case TOGGLE_FULL_PAGE: {
|
||||
const fullPage = !state.fullPage
|
||||
|
||||
return {...state, fullPage}
|
||||
}
|
||||
|
||||
case CHANGE_PAGE_ORIENTATION: {
|
||||
const {orientation} = action
|
||||
const isVertical = orientation === PAGE_ORIENTATION_PORTRAIT
|
||||
|
||||
return {...state, isVertical}
|
||||
}
|
||||
|
||||
case FOCUS_PROJECT: {
|
||||
let fullPage = state.fullPage
|
||||
|
||||
const projectName = action.projectName
|
||||
fullPage = !projectName
|
||||
? false
|
||||
: fullPage
|
||||
|
||||
return {...state, fullPage}
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
projects,
|
||||
page,
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export {default} from './App'
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Draft v0.10.5
|
||||
*
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
.DraftEditor-editorContainer,.DraftEditor-root,.public-DraftEditor-content{height:inherit;text-align:initial}.public-DraftEditor-content[contenteditable=true]{-webkit-user-modify:read-write-plaintext-only}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:1}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol,.public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4}
|
||||
@@ -0,0 +1,313 @@
|
||||
import React from 'react'
|
||||
import Immutable from 'immutable'
|
||||
import {
|
||||
DefaultDraftBlockRenderMap,
|
||||
EditorState,
|
||||
RichUtils,
|
||||
convertFromRaw,
|
||||
getDefaultKeyBinding,
|
||||
AtomicBlockUtils,
|
||||
Modifier
|
||||
} from 'draft-js'
|
||||
import DraftEditor, {composeDecorators} from 'draft-js-plugins-editor'
|
||||
|
||||
import createImagePlugin from 'draft-js-image-plugin'
|
||||
import createAlignmentPlugin from 'draft-js-alignment-plugin'
|
||||
import createFocusPlugin from 'draft-js-focus-plugin'
|
||||
import createResizeablePlugin from 'draft-js-resizeable-plugin'
|
||||
import createBlockDndPlugin from 'draft-js-drag-n-drop-plugin'
|
||||
// import createDragNDropUploadPlugin from 'draft-js-drag-n-drop-upload-plugin'
|
||||
import createDndFileUploadPlugin from '@mikeljames/draft-js-drag-n-drop-upload-plugin'
|
||||
|
||||
// import './Draft.less'
|
||||
import 'draft-js/dist/Draft.css'
|
||||
import 'draft-js-image-plugin/lib/plugin.css'
|
||||
|
||||
const focusPlugin = createFocusPlugin()
|
||||
const resizeablePlugin = createResizeablePlugin()
|
||||
const blockDndPlugin = createBlockDndPlugin()
|
||||
const alignmentPlugin = createAlignmentPlugin()
|
||||
const {AlignmentTool} = alignmentPlugin
|
||||
|
||||
const decorator = composeDecorators(
|
||||
resizeablePlugin.decorator,
|
||||
alignmentPlugin.decorator,
|
||||
focusPlugin.decorator,
|
||||
blockDndPlugin.decorator
|
||||
)
|
||||
const imagePlugin = createImagePlugin({decorator})
|
||||
|
||||
const dndFileUploadPlugin = createDndFileUploadPlugin({
|
||||
handleUpload: console.log,
|
||||
addImage: imagePlugin.addImage,
|
||||
})
|
||||
// const dragNDropFileUploadPlugin = createDragNDropUploadPlugin({
|
||||
// handleUpload: mockUpload,
|
||||
// addImage: imagePlugin.addImage,
|
||||
// })
|
||||
|
||||
const plugins = [
|
||||
dndFileUploadPlugin,
|
||||
blockDndPlugin,
|
||||
focusPlugin,
|
||||
alignmentPlugin,
|
||||
resizeablePlugin,
|
||||
imagePlugin
|
||||
]
|
||||
|
||||
const initialState = {
|
||||
'entityMap': {
|
||||
'0': {
|
||||
'type': 'IMAGE',
|
||||
'mutability': 'IMMUTABLE',
|
||||
'data': {
|
||||
'src': 'https://static.shlisselburg.org/art/images/7c206b4e211bb8486801fd84a3bf309b-big.jpg'
|
||||
}
|
||||
}
|
||||
},
|
||||
'blocks': [
|
||||
{
|
||||
'key': '9gm3s',
|
||||
'text': 'You can have images in your text field. This is a very rudimentary example, but you can enhance the image plugin with resizing, focus or alignment plugins.',
|
||||
'type': 'unstyled',
|
||||
'depth': 0,
|
||||
'inlineStyleRanges': [],
|
||||
'entityRanges': [],
|
||||
'data': {}
|
||||
},
|
||||
{
|
||||
'key': 'ov7r',
|
||||
'text': ' ',
|
||||
'type': 'atomic',
|
||||
'depth': 0,
|
||||
'inlineStyleRanges': [],
|
||||
'entityRanges': [{
|
||||
'offset': 0,
|
||||
'length': 1,
|
||||
'key': 0
|
||||
}],
|
||||
'data': {}
|
||||
},
|
||||
{
|
||||
'key': 'e23a8',
|
||||
'text': 'See advanced examples further down …',
|
||||
'type': 'unstyled',
|
||||
'depth': 0,
|
||||
'inlineStyleRanges': [],
|
||||
'entityRanges': [],
|
||||
'data': {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
class MyCustomBlock extends React.Component {
|
||||
// constructor(props) {
|
||||
// super(props)
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='MyCustomBlock'>
|
||||
{/* here, this.props.children contains a <section> container, as that was the matching element */}
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class MediaComponent extends React.Component {
|
||||
render() {
|
||||
const {block, contentState} = this.props
|
||||
const {foo} = this.props.blockProps
|
||||
const data = contentState.getEntity(block.getEntityAt(0)).getData()
|
||||
|
||||
console.log(data)
|
||||
|
||||
return (
|
||||
<i>
|
||||
{data.text}
|
||||
<img src={data.text} alt=""/>
|
||||
</i>
|
||||
)
|
||||
|
||||
// Return a <figure> or some other content using this data.
|
||||
}
|
||||
}
|
||||
|
||||
function blockRendererFn(contentBlock) {
|
||||
const type = contentBlock.getType()
|
||||
|
||||
if (type === 'atomic') {
|
||||
console.log('blockRendererFn', contentBlock.getKey(), type)
|
||||
|
||||
return {
|
||||
component: MediaComponent,
|
||||
editable: false,
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const blockRenderMap = Immutable.Map({
|
||||
'section': {
|
||||
element: 'section',
|
||||
wrapper: MyCustomBlock,
|
||||
}
|
||||
})
|
||||
|
||||
// Include 'paragraph' as a valid block and updated the unstyled element but
|
||||
// keep support for other draft default block types
|
||||
const extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(blockRenderMap)
|
||||
|
||||
export default class Editor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
editorState: EditorState.createWithContent(convertFromRaw(initialState)),
|
||||
}
|
||||
this.onChange = this.onChange.bind(this)
|
||||
this.onBoldClick = () => {
|
||||
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'))
|
||||
}
|
||||
this.focus = () => {
|
||||
this.editor.focus()
|
||||
}
|
||||
|
||||
this.onZomboClick = () => {
|
||||
const contentState = this.state.editorState.getCurrentContent();
|
||||
const contentStateWithEntity = contentState.createEntity(
|
||||
'LINK',
|
||||
'MUTABLE',
|
||||
{url: 'http://www.zombo.com'}
|
||||
)
|
||||
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
|
||||
const contentStateWithLink = Modifier.applyEntity(
|
||||
contentStateWithEntity,
|
||||
selectionState,
|
||||
entityKey
|
||||
)
|
||||
this.onChange(contentStateWithLink);
|
||||
|
||||
// this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'))
|
||||
}
|
||||
|
||||
this.handleKeyCommand = this.handleKeyCommand.bind(this)
|
||||
this.keyBindingFn = this.keyBindingFn.bind(this)
|
||||
this.insertBlock = this.insertBlock.bind(this)
|
||||
}
|
||||
|
||||
insertBlock(type, data) {
|
||||
const {editorState} = this.state
|
||||
const contentState = editorState.getCurrentContent()
|
||||
const contentStateWithEntity = contentState.createEntity(type, 'IMMUTABLE', data)
|
||||
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
||||
|
||||
const newEditorState = EditorState.set(editorState, {
|
||||
currentContent: contentStateWithEntity
|
||||
})
|
||||
|
||||
this.setState({
|
||||
editorState: AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' '),
|
||||
})
|
||||
}
|
||||
|
||||
onChange(editorState) {
|
||||
const content = editorState.getCurrentContent()
|
||||
const s = editorState.getSelection()
|
||||
|
||||
const key = s.getStartKey()
|
||||
const prevKey = content.getKeyBefore(key)
|
||||
|
||||
const block = content.getBlockForKey(key)
|
||||
const prevBlock = content.getBlockForKey(prevKey)
|
||||
|
||||
const text = block.getText()
|
||||
if (/\.jpg/.test(text)) {
|
||||
this.insertBlock('jpg', {text})
|
||||
// const newContent = Modifier.setBlockType(content, s, 'atomic')
|
||||
//
|
||||
// const newEditorState = EditorState.set(editorState, {
|
||||
// currentContent: newContent,
|
||||
// })
|
||||
//
|
||||
// this.setState({
|
||||
// editorState: newEditorState,
|
||||
// })
|
||||
} else {
|
||||
this.setState({editorState})
|
||||
}
|
||||
|
||||
// const blockData = block.getData()
|
||||
|
||||
// console.log(block.getText(), block.getType())
|
||||
|
||||
// console.log(block.getEntityAt(0))
|
||||
}
|
||||
|
||||
keyBindingFn(e) {
|
||||
// console.log("K", e.key)
|
||||
|
||||
// if (e.key === 'Enter') {
|
||||
// return 'enter'
|
||||
// }
|
||||
return getDefaultKeyBinding(e)
|
||||
}
|
||||
|
||||
handleKeyCommand(command) {
|
||||
console.log('CMD', command)
|
||||
const newState = RichUtils.handleKeyCommand(this.state.editorState, command)
|
||||
|
||||
if (newState) {
|
||||
this.onChange(newState)
|
||||
return 'handled'
|
||||
}
|
||||
|
||||
return 'not-handled'
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = this.state.editorState.getCurrentContent()
|
||||
const e = this.state.editorState
|
||||
|
||||
const s = e.getSelection()
|
||||
const key = s.getStartKey()
|
||||
const so = s.getStartOffset()
|
||||
const block = content.getBlockForKey(key)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DraftEditor
|
||||
editorState={this.state.editorState}
|
||||
onChange={this.onChange}
|
||||
blockRendererFn={blockRendererFn}
|
||||
blockRenderMap={extendedBlockRenderMap}
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
keyBindingFn={this.keyBindingFn}
|
||||
plugins={plugins}
|
||||
ref={(element) => {
|
||||
this.editor = element
|
||||
}}
|
||||
/>
|
||||
{/* <AlignmentTool/> */}
|
||||
<button onClick={this.onBoldClick}>Bold</button>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
block key: {key} type: {block.getType()}
|
||||
</p>
|
||||
<p>
|
||||
start offset {so}
|
||||
</p>
|
||||
<pre>
|
||||
{content.getPlainText('\n')}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
export {default} from './Editor'
|
||||
@@ -0,0 +1,78 @@
|
||||
import React, {Component} from 'react'
|
||||
|
||||
export default class ContentEditable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.emitChange = this.emitChange.bind(this)
|
||||
this.onRef = this.onRef.bind(this)
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
return this.props.children
|
||||
}
|
||||
|
||||
onRef(element) {
|
||||
this.htmlEl = element
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
if (!this.htmlEl) return
|
||||
const html = this.htmlEl.innerHTML
|
||||
|
||||
if (html !== this.lastHtml) this.props.onChange(html)
|
||||
this.lastHtml = html
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.htmlEl.focus()
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
let {props, htmlEl} = this
|
||||
|
||||
// We need not rerender if the change of props simply reflects the user's edits.
|
||||
// Rerendering in this case would make the cursor/caret jump
|
||||
|
||||
// Rerender if there is no element yet... (somehow?)
|
||||
if (!this.htmlEl) return true
|
||||
|
||||
// // ...or if html really changed... (programmatically, not by user edit)
|
||||
// if (nextProps.html !== htmlEl.innerHTML && nextProps.html !== props.html) {
|
||||
// return true
|
||||
// }
|
||||
|
||||
return false
|
||||
|
||||
// let optional = ['style', 'className', 'disabled', 'tagName']
|
||||
//
|
||||
// // Handle additional properties
|
||||
// return optional.some(name => props[name] !== nextProps[name])
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const html = this.getHtml()
|
||||
if (this.htmlEl && this.htmlEl.innerHTML !== html) {
|
||||
// Perhaps React (whose VDOM gets outdated because we often prevent
|
||||
// rerendering) did not update the DOM. So we update it manually now.
|
||||
this.htmlEl.innerHTML = html
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {mix} = this.props;
|
||||
|
||||
return <div
|
||||
className={mix}
|
||||
ref={this.onRef}
|
||||
onInput={this.emitChange}
|
||||
onChange={this.emitChange}
|
||||
// onBlur={this.props.onBlur || this.emitChange}
|
||||
contentEditable={true}
|
||||
suppressContentEditableWarning={true}
|
||||
// dangerouslySetInnerHTML={{__html: this.getHtml()}}
|
||||
>
|
||||
{this.getHtml()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import React, {Component} from 'react'
|
||||
import {MarkerTabIndex} from './MarkerTabIndex'
|
||||
import {MarkerCell} from './MarkerCell'
|
||||
|
||||
import './Marker.less'
|
||||
import {exist} from '../../lib/fn'
|
||||
import ContentEditable from './ContentEditable'
|
||||
import {constrain} from "./lib/fn";
|
||||
|
||||
export class Marker extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
currentCellIndex: 0,
|
||||
}
|
||||
|
||||
this.onCellUpdate = this.onCellUpdate.bind(this)
|
||||
this.onCellSelect = this.onCellSelect.bind(this)
|
||||
this.onKeyDown = this.onKeyDown.bind(this)
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp': {
|
||||
console.log('Marker ArrowUp')
|
||||
|
||||
this.setState({
|
||||
currentCellIndex: this.getPrevCellIndex(),
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
console.log('Marker ArrowDown')
|
||||
|
||||
this.setState({
|
||||
currentCellIndex: this.getNextCellIndex(),
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'Enter': {
|
||||
console.log('Marker Enter')
|
||||
|
||||
const s = getSelection()
|
||||
console.log(s, s.getRangeAt(0))
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
// if (!event.shiftKey) return
|
||||
// event.stopPropagation()
|
||||
|
||||
return this.handleEnter()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getCellIndex(n) {
|
||||
const {data} = this.props
|
||||
const {currentCellIndex} = this.state
|
||||
const index = currentCellIndex + n
|
||||
|
||||
return constrain(index, 0, data.length - 1)
|
||||
}
|
||||
|
||||
getPrevCellIndex(...args) {
|
||||
return this.getCellIndex(-1, ...args)
|
||||
}
|
||||
|
||||
getNextCellIndex(...args) {
|
||||
return this.getCellIndex(1, ...args)
|
||||
}
|
||||
|
||||
onCellSelect(index) {
|
||||
this.setState({
|
||||
currentCellIndex: index,
|
||||
})
|
||||
}
|
||||
|
||||
getDataAt(index) {
|
||||
return this.props.data[index]
|
||||
}
|
||||
|
||||
getBlankTextAt(index) {
|
||||
if (index === 0) return ''
|
||||
|
||||
const cellBefore = this.getDataAt(index)
|
||||
const textBefore = cellBefore.data
|
||||
|
||||
let text
|
||||
|
||||
const ol = (text) => {
|
||||
const m = /^(\d+)\.\s/.exec(text)
|
||||
if (m) {
|
||||
const d = parseFloat(m[1])
|
||||
return `${d + 1}.`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const ul = (text) => /^-\s/.test(text)
|
||||
? '- '
|
||||
: null
|
||||
|
||||
text = ol(textBefore)
|
||||
if (text) return text
|
||||
|
||||
text = ul(textBefore)
|
||||
if (text) return text
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
async handleEnter() {
|
||||
const {currentCellIndex} = this.state
|
||||
const currentCell = this.getDataAt(currentCellIndex)
|
||||
|
||||
if (currentCell.data) {
|
||||
const nextCellIndex = currentCellIndex + 1
|
||||
|
||||
if (nextCellIndex > this.props.data.length - 1) {
|
||||
const {data, marker, onUpdate} = this.props
|
||||
const blankText = this.getBlankTextAt(currentCellIndex)
|
||||
const newCell = await marker.blank({text: blankText})
|
||||
const newData = [
|
||||
...data,
|
||||
newCell,
|
||||
]
|
||||
|
||||
onUpdate(newData)
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentCellIndex: nextCellIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async onCellUpdate(index, text) {
|
||||
console.log('UPDATE', text)
|
||||
|
||||
const {data, marker, onUpdate} = this.props
|
||||
|
||||
const cells = await marker.evaluate(text)
|
||||
|
||||
const before = data.slice(0, index)
|
||||
const after = data.slice(index + 1)
|
||||
|
||||
let newData = [
|
||||
...before,
|
||||
...cells,
|
||||
...after,
|
||||
]
|
||||
|
||||
// const next = index + cells.length
|
||||
// if (next > newData.length - 1) {
|
||||
// const newCell = await marker.blank()
|
||||
// newData.push(newCell)
|
||||
// }
|
||||
|
||||
console.log(newData.map(x => x.data).join('\n'))
|
||||
|
||||
onUpdate(newData)
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// this.setState({
|
||||
// currentCellIndex: 0,
|
||||
// })
|
||||
}
|
||||
|
||||
render() {
|
||||
const {data} = this.props
|
||||
if (!data.length) return null
|
||||
|
||||
const {currentCellIndex} = this.state
|
||||
|
||||
const tabIndex = 1
|
||||
return (
|
||||
<div
|
||||
className='Marker'
|
||||
// onKeyDownCapture={this.onKeyDown}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
{data.map((x, i) => {
|
||||
return (i === currentCellIndex ? (
|
||||
<ContentEditable
|
||||
mix={'MarkerCell'}
|
||||
tabIndex={tabIndex + i}
|
||||
key={i}
|
||||
onChange={text => this.onCellUpdate(i, text)}
|
||||
>
|
||||
{x.data}
|
||||
</ContentEditable>
|
||||
) : (
|
||||
<div
|
||||
className={'MarkerCell'}
|
||||
tabIndex={tabIndex + i}
|
||||
key={i}
|
||||
onFocus={() => this.onCellSelect(i)}
|
||||
>
|
||||
{x.render()}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
.Marker {
|
||||
//--active-color: #73acf6;
|
||||
--active-color: #f0f0f0;
|
||||
--cell-padding: 8px;
|
||||
--font-size: 16px;
|
||||
|
||||
font-size: var(--font-size);
|
||||
font-family: sans-serif;
|
||||
//line-height: var(--font-size);
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: var(--font-size);
|
||||
font-family: sans-serif;
|
||||
padding: 0;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
height: var(--font-size);
|
||||
//height: calc(2px + var(--font-size));
|
||||
border: none;
|
||||
background-color: var(--active-color);
|
||||
|
||||
outline: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.MarkerCellTabIndex {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.MarkerCell {
|
||||
//padding: var(--cell-padding);
|
||||
padding-bottom: 8px;
|
||||
|
||||
position: relative;
|
||||
|
||||
&--active {
|
||||
background-color: var(--active-color);
|
||||
//background-color: #f6903b;
|
||||
}
|
||||
|
||||
&--edit {
|
||||
div {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
//background-color: #00b7ec;
|
||||
background-color: var(--active-color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React, {Component} from 'react'
|
||||
import className from 'classnames'
|
||||
import {trimNewline} from './lib/fn'
|
||||
|
||||
import './Marker.less'
|
||||
import {MarkerEdit} from './MarkerEdit'
|
||||
|
||||
const Cell = ({children, active = false, edit = false}) => (
|
||||
<div className={className('MarkerCell', {
|
||||
'MarkerCell--active': active,
|
||||
'MarkerCell--edit': edit,
|
||||
})}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export class MarkerCell extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this)
|
||||
this.onChange = this.onChange.bind(this)
|
||||
|
||||
this.state = {
|
||||
content: null,
|
||||
}
|
||||
}
|
||||
|
||||
getCellData() {
|
||||
return this.state.content
|
||||
? this.state.content
|
||||
: this.props.cell.data
|
||||
}
|
||||
|
||||
getCellComponent() {
|
||||
return this.props.cell.render()
|
||||
}
|
||||
|
||||
onSubmit(e) {
|
||||
e.preventDefault()
|
||||
const {onUpdate} = this.props
|
||||
onUpdate(this.getCellData())
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
const content = trimNewline(e.target.value)
|
||||
|
||||
this.setState({
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {active, edit} = this.props
|
||||
|
||||
return edit
|
||||
? (
|
||||
<Cell edit={edit}>
|
||||
<MarkerEdit
|
||||
onSubmit={this.onSubmit}
|
||||
onChange={this.onChange}
|
||||
>
|
||||
{this.getCellData()}
|
||||
</MarkerEdit>
|
||||
</Cell>
|
||||
) : (
|
||||
<Cell active={active}>
|
||||
{this.getCellComponent()}
|
||||
</Cell>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, {Component, createContext} from 'react'
|
||||
import className from 'classnames'
|
||||
import {trim} from './lib/fn'
|
||||
import {MarkerContext} from './Marker'
|
||||
|
||||
import './Marker.less'
|
||||
import ContentEditable from './ContentEditable'
|
||||
|
||||
export class MarkerEdit extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// this.onInputRef = this.onInputRef.bind(this)
|
||||
this.onChange = this.onChange.bind(this)
|
||||
this.onFocus = this.onFocus.bind(this)
|
||||
this.onKeyDown = this.onKeyDown.bind(this)
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
console.log(event, event.target)
|
||||
this.props.onChange(event)
|
||||
}
|
||||
|
||||
onFocus(e) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp': {
|
||||
if (event.shiftKey) return
|
||||
event.stopPropagation()
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
if (event.shiftKey) return
|
||||
event.stopPropagation()
|
||||
break
|
||||
}
|
||||
|
||||
case 'Enter': {
|
||||
console.log('MarkerEdit Enter')
|
||||
if (event.shiftKey) return
|
||||
// event.stopPropagation()
|
||||
|
||||
const {onSubmit} = this.props
|
||||
onSubmit(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// onInputRef(e) {
|
||||
// this.input = e
|
||||
// }
|
||||
//
|
||||
// componentDidMount() {
|
||||
// this.input.focus()
|
||||
// }
|
||||
|
||||
render() {
|
||||
const {children} = this.props
|
||||
|
||||
return (
|
||||
<ContentEditable
|
||||
// ref={this.onInputRef}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
{children}
|
||||
</ContentEditable>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <textarea
|
||||
// value={children}
|
||||
// ref={this.onInputRef}
|
||||
// onChange={onChange}
|
||||
// onFocus={this.onFocus}
|
||||
// onKeyDown={this.onKeyDown}
|
||||
// />
|
||||
// )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React, {Component, createContext} from 'react'
|
||||
|
||||
import './Marker.less'
|
||||
|
||||
export class MarkerTabIndex extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.onFocus = this.onFocus.bind(this)
|
||||
this.onRef = this.onRef.bind(this)
|
||||
}
|
||||
|
||||
onFocus() {
|
||||
const {onFocus, data} = this.props
|
||||
onFocus(data)
|
||||
}
|
||||
|
||||
onRef(e) {
|
||||
this.element = e
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const {focus} = this.props
|
||||
if (focus) {
|
||||
this.element.focus()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {tabIndex, children} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'MarkerCellTabIndex'}
|
||||
children={children}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={this.onFocus}
|
||||
ref={this.onRef}
|
||||
role={'button'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
|
||||
import './Marker.less'
|
||||
|
||||
export const MarkerWidgetText = ({text}) => (
|
||||
<div
|
||||
className='MarkerWidgetText'
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const MarkerWidgetHtml = ({html}) => (
|
||||
<div
|
||||
className='MarkerWidgetHtml'
|
||||
dangerouslySetInnerHTML={{__html: html}}
|
||||
/>
|
||||
)
|
||||
|
||||
export const MarkerWidgetUrl = ({url, text}) => (
|
||||
<div
|
||||
className='MarkerWidgetUrl'
|
||||
>
|
||||
<a href={url}>
|
||||
{text}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
export {Marker as default} from './Marker'
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Remove trailing spaces from string
|
||||
*
|
||||
* f(_x_) => x
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
export function trim(str) {
|
||||
return str
|
||||
.replace(/^\s+/, '')
|
||||
.replace(/\s+$/, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove trailing spaces from string
|
||||
*
|
||||
* f(_x_) => x
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
export function trimNewline(str) {
|
||||
return str
|
||||
.replace(/^\n+/, '')
|
||||
.replace(/\n+$/, '')
|
||||
}
|
||||
|
||||
export function constrain(n, min, max) {
|
||||
if (n < min) return min
|
||||
if (n > max) return max
|
||||
return n
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
|
||||
import Token from './Token'
|
||||
import {trim} from '../fn'
|
||||
import {getHtml} from './index'
|
||||
|
||||
|
||||
export default class FileToken extends Token {
|
||||
static test(data) {
|
||||
return is_file(data, documentExtensions + imageExtensions)
|
||||
}
|
||||
|
||||
constructor({data}) {
|
||||
super({
|
||||
name: 'file',
|
||||
data,
|
||||
})
|
||||
this.joinable = false
|
||||
}
|
||||
|
||||
async compile() {
|
||||
const data = parse_file(this.getData())
|
||||
const url = data['file']
|
||||
const text = data.caption
|
||||
? data.caption
|
||||
: url
|
||||
|
||||
return (
|
||||
<a href={url}>
|
||||
{text}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react'
|
||||
|
||||
import Token from './Token'
|
||||
import {isImage, parseFile} from './index'
|
||||
|
||||
export class ImageToken extends Token {
|
||||
static test(data) {
|
||||
return isImage(data)
|
||||
}
|
||||
|
||||
constructor({data}) {
|
||||
super({
|
||||
name: 'image',
|
||||
data,
|
||||
})
|
||||
this.joinable = true
|
||||
this.build = null
|
||||
}
|
||||
|
||||
merge(token) {
|
||||
const mergedToken = new ImageCollectionToken([this.data, token.data])
|
||||
mergedToken.build = this.build
|
||||
return mergedToken
|
||||
}
|
||||
|
||||
async compile() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageCollectionToken extends ImageToken {
|
||||
constructor({data}) {
|
||||
super({data})
|
||||
this.name = 'image_collection'
|
||||
this.build = null
|
||||
}
|
||||
|
||||
merge(token) {
|
||||
const data = Array.isArray(token.getData())
|
||||
? token.getData()
|
||||
: [token.getData()]
|
||||
|
||||
const mergedToken = new ImageCollectionToken({
|
||||
data: [...self.getData(), ...data]
|
||||
})
|
||||
mergedToken.build = self.build
|
||||
return mergedToken
|
||||
}
|
||||
|
||||
async compile() {
|
||||
const data = this.parseData()
|
||||
let images = []
|
||||
for (let x of data) {
|
||||
const img = await this.build(x)
|
||||
images.push(img)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='kazimir__image-collection'>
|
||||
<div
|
||||
className='fotorama'
|
||||
data-width='100%'
|
||||
data-ratio='800/600'
|
||||
>
|
||||
{images.map(({src}) => (
|
||||
<img src={src}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
parseData() {
|
||||
return this
|
||||
.getData()
|
||||
.map(parseFile)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import SplitToken from './SplitToken'
|
||||
|
||||
export class Marker {
|
||||
constructor() {
|
||||
this.tokenFactories = []
|
||||
this.treeMiddlewares = []
|
||||
|
||||
this.join = true
|
||||
this.blankFactory = null
|
||||
}
|
||||
|
||||
async blank({text}) {
|
||||
if (!this.blankFactory) {
|
||||
throw new Error(`Blank factory is not defined`)
|
||||
}
|
||||
const token = await this.blankFactory.create(text)
|
||||
return token.compile()
|
||||
}
|
||||
|
||||
addTokenFactory(tokenFactory, {blank = false} = {}) {
|
||||
this.tokenFactories.push(tokenFactory)
|
||||
if (blank) {
|
||||
this.blankFactory = tokenFactory
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
addTreeMiddleware(callback) {
|
||||
this.treeMiddlewares.push(callback)
|
||||
return this
|
||||
}
|
||||
|
||||
async evaluate(text) {
|
||||
let tokens = text.split('\n')
|
||||
tokens = this.markupTokens(tokens)
|
||||
if (this.join) {
|
||||
tokens = this.joinTokens(tokens)
|
||||
}
|
||||
|
||||
return compileTokens(tokens)
|
||||
// tree = lxml.html.fromstring(compiled)
|
||||
// return await self.process_tree(tree)
|
||||
}
|
||||
|
||||
joinTokens(tokens) {
|
||||
return mergeTokens(joinTokens(tokens))
|
||||
}
|
||||
|
||||
async processTree(tree) {
|
||||
// for fn in self.treeMiddlewares:
|
||||
// tree = await fn(tree)
|
||||
// return tree
|
||||
}
|
||||
|
||||
markupTokens(tokens) {
|
||||
const markedTokens = []
|
||||
|
||||
tokens.forEach(data => {
|
||||
const token = this.createToken(data)
|
||||
if (!token) {
|
||||
throw new Error(`Token not recognized for data ${data}`)
|
||||
}
|
||||
|
||||
markedTokens.push(token)
|
||||
})
|
||||
|
||||
return markedTokens
|
||||
}
|
||||
|
||||
createToken(data) {
|
||||
for (let factory of this.tokenFactories) {
|
||||
if (factory.test(data)) {
|
||||
return factory.create(data)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function joinTokens(tokens) {
|
||||
const isSplit = x => x instanceof SplitToken
|
||||
|
||||
let bufferTag = null
|
||||
let tokensBuffer = []
|
||||
let tokensList = []
|
||||
for (let token of tokens) {
|
||||
const tag = token.name
|
||||
const changed = bufferTag !== null && tag !== bufferTag
|
||||
|
||||
let flush = isSplit(token)
|
||||
if (changed) {
|
||||
flush = true
|
||||
}
|
||||
|
||||
if (!token.isJoinable()) {
|
||||
flush = true
|
||||
}
|
||||
|
||||
if (flush) {
|
||||
if (tokensBuffer.length) {
|
||||
tokensList.push(tokensBuffer)
|
||||
}
|
||||
|
||||
if (isSplit(token)) {
|
||||
tokensBuffer = []
|
||||
bufferTag = null
|
||||
} else {
|
||||
tokensBuffer = [token]
|
||||
bufferTag = tag
|
||||
}
|
||||
} else {
|
||||
bufferTag = tag
|
||||
tokensBuffer.push(token)
|
||||
}
|
||||
}
|
||||
|
||||
if (tokensBuffer.length) {
|
||||
tokensList.push(tokensBuffer)
|
||||
}
|
||||
|
||||
return tokensList
|
||||
}
|
||||
|
||||
function mergeTokens(tokensList) {
|
||||
return tokensList
|
||||
.map(ts => ts
|
||||
.reduce(
|
||||
(token, x) => token.merge(x)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async function compileTokens(tokens) {
|
||||
return Promise.all(
|
||||
tokens.map(x => x.compile())
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import Token from './Token'
|
||||
import {trim} from '../fn'
|
||||
|
||||
export default class SplitToken extends Token {
|
||||
static test(data) {
|
||||
data = trim(data)
|
||||
return data === ''
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: 'split',
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return null
|
||||
}
|
||||
|
||||
merge(token) {
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import Token from './Token'
|
||||
import {trim} from '../fn'
|
||||
import {getHtml} from './index'
|
||||
|
||||
export default class TextToken extends Token {
|
||||
static test(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: 'text',
|
||||
})
|
||||
}
|
||||
|
||||
merge(token) {
|
||||
const data = [this.data, token.data].join('\n')
|
||||
|
||||
return new TextToken(this.getMergeOptions({
|
||||
data: trim(data),
|
||||
}))
|
||||
}
|
||||
|
||||
async getData() {
|
||||
const html = getHtml(this.data)
|
||||
return {html}
|
||||
}
|
||||
|
||||
// async compile() {
|
||||
// // return getHtml(this.getData())
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// return (
|
||||
// <div
|
||||
// className='MarkerWidgetHtml'
|
||||
// dangerouslySetInnerHTML={{__html: html}}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
export default class Token {
|
||||
static test(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
constructor({name, data, component}) {
|
||||
this.name = name
|
||||
this.data = data
|
||||
this.component = component
|
||||
this.joinable = true
|
||||
|
||||
this.compiledData = null
|
||||
}
|
||||
|
||||
isJoinable() {
|
||||
return this.joinable
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.compiledData) {
|
||||
throw new Error('Token not compiled. Call Token::compile before.')
|
||||
}
|
||||
return this.component(this.compiledData)
|
||||
}
|
||||
|
||||
async getData() {
|
||||
return this.data
|
||||
}
|
||||
|
||||
async compile() {
|
||||
this.compiledData = await this.getData()
|
||||
return this
|
||||
}
|
||||
|
||||
merge(token) {
|
||||
return this
|
||||
}
|
||||
|
||||
getMergeOptions(extend) {
|
||||
return {
|
||||
name: this.name,
|
||||
data: this.data,
|
||||
component: this.component,
|
||||
...extend,
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `[Token ${this.name} ${this.data}]`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export class TokenFactory {
|
||||
constructor({token, component}) {
|
||||
this.token = token
|
||||
this.component = component
|
||||
}
|
||||
|
||||
test(sample) {
|
||||
return this.token.test(sample)
|
||||
}
|
||||
|
||||
create(data) {
|
||||
const Token = this.token
|
||||
return new Token({
|
||||
data,
|
||||
component: this.component,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildTokenFactory extends TokenFactory {
|
||||
constructor({token, build, component}) {
|
||||
super({token})
|
||||
this.build = build
|
||||
this.component = component
|
||||
}
|
||||
|
||||
create(data) {
|
||||
const Token = this.token
|
||||
return new Token({
|
||||
data,
|
||||
component: this.component,
|
||||
build: this.build,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Token from './Token'
|
||||
import {isUrl} from './index'
|
||||
|
||||
export default class UrlToken extends Token {
|
||||
static test(data) {
|
||||
return isUrl(data)
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: 'url',
|
||||
})
|
||||
this.joinable = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {isUrl} from './index'
|
||||
import React from 'react'
|
||||
import UrlToken from './UrlToken'
|
||||
|
||||
export default class YoutubeToken extends UrlToken {
|
||||
static test(data) {
|
||||
if (!isUrl(data)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const url = new URL(data)
|
||||
return ['www.youtube.com', 'youtube.com']
|
||||
.includes(url.hostname)
|
||||
}
|
||||
|
||||
async getData() {
|
||||
const url = new URL(this.data)
|
||||
|
||||
const youtubeId = url.searchParams.get('v')
|
||||
return {
|
||||
url: `//www.youtube.com/embed/${youtubeId}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import {markdown} from 'markdown'
|
||||
|
||||
const {toHTML: markdownToHtml} = markdown
|
||||
|
||||
const splitExt = s => {
|
||||
const parts = s.split('.')
|
||||
const name = parts.splice(0, parts.length - 1)
|
||||
const ext = parts[parts.length - 1]
|
||||
return {name, ext}
|
||||
}
|
||||
|
||||
export const imageExtensions = ['.jpg', '.jpeg', '.png']
|
||||
export const documentExtensions = ['.doc', '.docx', '.pdf', '.xls', '.xlsx']
|
||||
|
||||
export function getHtml(text) {
|
||||
return markdownToHtml(text)
|
||||
}
|
||||
|
||||
export function isUrl(sample) {
|
||||
try {
|
||||
return Boolean(new URL(sample))
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isImage(sample) {
|
||||
const file = parseFile(sample)['file']
|
||||
return isFile(file, imageExtensions)
|
||||
}
|
||||
|
||||
export function isDocument(sample) {
|
||||
const file = parseFile(sample)['file']
|
||||
return isFile(file, documentExtensions)
|
||||
}
|
||||
|
||||
export function isFile(sample, extensions) {
|
||||
let {name, ext} = splitExt(sample)
|
||||
ext = ext.toLowerCase()
|
||||
return extensions.include(ext)
|
||||
}
|
||||
|
||||
export function parseFile(sample) {
|
||||
const re = /)/
|
||||
const p = re.exec(sample)
|
||||
let caption
|
||||
let file
|
||||
if (p) {
|
||||
caption = p.group(1)
|
||||
file = p.group(2)
|
||||
} else {
|
||||
sample = sample.split(' ')
|
||||
file = sample[0]
|
||||
caption = ' '.join(sample.substr(1))
|
||||
}
|
||||
|
||||
return {
|
||||
file,
|
||||
caption,
|
||||
}
|
||||
}
|
||||
|
||||
export function parseLink(sample) {
|
||||
const re = /)/
|
||||
const p = re.exec(sample)
|
||||
|
||||
if (!p) {
|
||||
return null
|
||||
}
|
||||
|
||||
const url = p[2]
|
||||
return {
|
||||
url: new URL(url),
|
||||
alt: p[1],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
:root {
|
||||
--content-width-state-normal: 60%;
|
||||
--content-width-state-full: 100%;
|
||||
--content-width: var(--content-width-state-normal);
|
||||
|
||||
--module-m: .5em;
|
||||
}
|
||||
|
||||
html, body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: absolute;
|
||||
|
||||
background-color: #222;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Input Sans Compressed", sans-serif;
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
height: 100%;
|
||||
/*height: 100px;*/
|
||||
width: 100%;
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import App from './features/App'
|
||||
|
||||
// import {createDevTools} from 'redux-devtools'
|
||||
// import LogMonitor from 'redux-devtools-log-monitor'
|
||||
// import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
|
||||
import {Provider} from 'react-redux'
|
||||
import reducer from './reducers'
|
||||
import effects from './effects'
|
||||
|
||||
import {create} from './store'
|
||||
|
||||
import {Router, Route, browserHistory} from 'react-router'
|
||||
import {syncHistoryWithStore} from 'react-router-redux'
|
||||
|
||||
const initialState = {}
|
||||
|
||||
// const DevTools = createDevTools(
|
||||
// <DockMonitor defaultIsVisible={false}
|
||||
// toggleVisibilityKey="ctrl-h"
|
||||
// changePositionKey="ctrl-q"
|
||||
// >
|
||||
// <LogMonitor theme="tomorrow" preserveScrollTop={false}/>
|
||||
// </DockMonitor>
|
||||
// )
|
||||
|
||||
const store = create(reducer, initialState, [
|
||||
// DevTools.instrument()
|
||||
], effects())
|
||||
// const history = syncHistoryWithStore(browserHistory, store)
|
||||
|
||||
export default function (marker, data) {
|
||||
// return (
|
||||
// <App/>
|
||||
// )
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<div>
|
||||
<App
|
||||
marker={marker}
|
||||
data={data}
|
||||
/>
|
||||
{/*<Router history={history}>*/}
|
||||
{/*<Route path="/edit" component={App}>*/}
|
||||
|
||||
{/*</Route>*/}
|
||||
{/*</Router>*/}
|
||||
|
||||
{/*<DevTools/>*/}
|
||||
</div>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
import L from 'leaflet'
|
||||
|
||||
/*
|
||||
Leaflet.pattern, Provides tools to set the backgrounds of vector shapes in Leaflet to be patterns.
|
||||
https://github.com/teastman/Leaflet.pattern
|
||||
(c) 2015, Tyler Eastman
|
||||
*/
|
||||
(function (window, document) {
|
||||
/*
|
||||
* L.Pattern is the base class for fill patterns for leaflet Paths.
|
||||
*/
|
||||
|
||||
L.Pattern = L.Class.extend({
|
||||
// includes: [L.Mixin.Events],
|
||||
includes: [L.Evented],
|
||||
|
||||
options: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 8,
|
||||
height: 8,
|
||||
patternUnits: 'userSpaceOnUse',
|
||||
patternContentUnits: 'userSpaceOnUse'
|
||||
// angle: <0 - 360>
|
||||
// patternTransform: <transform-list>
|
||||
},
|
||||
|
||||
_addShapes: L.Util.falseFn,
|
||||
_update: L.Util.falseFn,
|
||||
|
||||
initialize: function (options) {
|
||||
this._shapes = {};
|
||||
L.setOptions(this, options);
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
this._map = map.target ? map.target : map;
|
||||
this._map._initDefRoot();
|
||||
|
||||
// Create the DOM Object for the pattern.
|
||||
this._initDom();
|
||||
|
||||
// Any shapes that were added before this was added to the map need to have their onAdd called.
|
||||
// for (let i of this._shapes) {
|
||||
// i.onAdd(this);
|
||||
// }
|
||||
Object
|
||||
.values(this._shapes)
|
||||
.forEach(x => x.onAdd(this))
|
||||
|
||||
// Call any children that want to add their own shapes.
|
||||
this._addShapes();
|
||||
|
||||
// Add the DOM Object to the DOM Tree
|
||||
this._addDom();
|
||||
this.redraw();
|
||||
|
||||
if (this.getEvents) {
|
||||
this._map.on(this.getEvents(), this);
|
||||
}
|
||||
this.fire('add');
|
||||
this._map.fire('patternadd', {pattern: this});
|
||||
},
|
||||
|
||||
onRemove: function () {
|
||||
this._removeDom();
|
||||
},
|
||||
|
||||
redraw: function () {
|
||||
if (this._map) {
|
||||
this._update();
|
||||
// for (let i of this._shapes) {
|
||||
// i.redraw();
|
||||
// }
|
||||
Object
|
||||
.values(this._shapes)
|
||||
.forEach(x => x.redraw())
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setStyle: function (style) {
|
||||
L.setOptions(this, style);
|
||||
if (this._map) {
|
||||
this._updateStyle();
|
||||
this.redraw();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
addTo: function (map) {
|
||||
map.addPattern(this);
|
||||
return this;
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
return this.removeFrom(this._map);
|
||||
},
|
||||
|
||||
removeFrom: function (map) {
|
||||
if (map) {
|
||||
map.removePattern(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
L.Map.addInitHook(function () {
|
||||
this._patterns = {};
|
||||
});
|
||||
|
||||
L.Map.include({
|
||||
addPattern: function (pattern) {
|
||||
var id = L.stamp(pattern);
|
||||
if (this._patterns[id]) { return pattern; }
|
||||
this._patterns[id] = pattern;
|
||||
|
||||
this.whenReady(pattern.onAdd, pattern);
|
||||
return this;
|
||||
},
|
||||
|
||||
removePattern: function (pattern) {
|
||||
var id = L.stamp(pattern);
|
||||
if (!this._patterns[id]) { return this; }
|
||||
|
||||
if (this._loaded) {
|
||||
pattern.onRemove(this);
|
||||
}
|
||||
|
||||
if (pattern.getEvents) {
|
||||
this.off(pattern.getEvents(), pattern);
|
||||
}
|
||||
|
||||
delete this._patterns[id];
|
||||
|
||||
if (this._loaded) {
|
||||
this.fire('patternremove', {pattern: pattern});
|
||||
pattern.fire('remove');
|
||||
}
|
||||
|
||||
pattern._map = null;
|
||||
return this;
|
||||
},
|
||||
|
||||
hasPattern: function (pattern) {
|
||||
return !!pattern && (L.stamp(pattern) in this._patterns);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
L.Pattern.SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
L.Pattern = L.Pattern.extend({
|
||||
_createElement: function (name) {
|
||||
return document.createElementNS(L.Pattern.SVG_NS, name);
|
||||
},
|
||||
|
||||
_initDom: function () {
|
||||
this._dom = this._createElement('pattern');
|
||||
if (this.options.className) {
|
||||
L.DomUtil.addClass(this._dom, this.options.className);
|
||||
}
|
||||
this._updateStyle();
|
||||
},
|
||||
|
||||
_addDom: function () {
|
||||
this._map._defRoot.appendChild(this._dom);
|
||||
},
|
||||
|
||||
_removeDom: function () {
|
||||
L.DomUtil.remove(this._dom);
|
||||
},
|
||||
|
||||
_updateStyle: function () {
|
||||
var dom = this._dom,
|
||||
options = this.options;
|
||||
|
||||
if (!dom) { return; }
|
||||
|
||||
dom.setAttribute('id', L.stamp(this));
|
||||
dom.setAttribute('x', options.x);
|
||||
dom.setAttribute('y', options.y);
|
||||
dom.setAttribute('width', options.width);
|
||||
dom.setAttribute('height', options.height);
|
||||
dom.setAttribute('patternUnits', options.patternUnits);
|
||||
dom.setAttribute('patternContentUnits', options.patternContentUnits);
|
||||
|
||||
if (options.patternTransform || options.angle) {
|
||||
var transform = options.patternTransform ? options.patternTransform + " " : "";
|
||||
transform += options.angle ? "rotate(" + options.angle + ") " : "";
|
||||
dom.setAttribute('patternTransform', transform);
|
||||
}
|
||||
else {
|
||||
dom.removeAttribute('patternTransform');
|
||||
}
|
||||
|
||||
// for (let i of this._shapes) {
|
||||
// i._updateStyle();
|
||||
// }
|
||||
|
||||
Object
|
||||
.values(this._shapes)
|
||||
.forEach(x => x._updateStyle())
|
||||
}
|
||||
});
|
||||
|
||||
L.Map.include({
|
||||
_initDefRoot: function () {
|
||||
if (!this._defRoot) {
|
||||
if (typeof this.getRenderer === 'function') {
|
||||
var renderer = this.getRenderer(this);
|
||||
this._defRoot = L.Pattern.prototype._createElement('defs');
|
||||
renderer._container.appendChild(this._defRoot);
|
||||
} else {
|
||||
if (!this._pathRoot) {
|
||||
this._initPathRoot();
|
||||
}
|
||||
this._defRoot = L.Pattern.prototype._createElement('defs');
|
||||
this._pathRoot.appendChild(this._defRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (L.SVG) {
|
||||
L.SVG.include({
|
||||
_superUpdateStyle: L.SVG.prototype._updateStyle,
|
||||
|
||||
_updateStyle: function (layer) {
|
||||
this._superUpdateStyle(layer);
|
||||
|
||||
if (layer.options.fill && layer.options.fillPattern) {
|
||||
layer._path.setAttribute('fill', 'url(#' + L.stamp(layer.options.fillPattern) + ")");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
L.Path.include({
|
||||
_superUpdateStyle: L.Path.prototype._updateStyle,
|
||||
|
||||
_updateStyle: function () {
|
||||
this._superUpdateStyle();
|
||||
|
||||
if (this.options.fill && this.options.fillPattern) {
|
||||
this._path.setAttribute('fill', 'url(#' + L.stamp(this.options.fillPattern) + ")");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* L.StripePattern is an implementation of Pattern that creates stripes.
|
||||
*/
|
||||
|
||||
L.StripePattern = L.Pattern.extend({
|
||||
|
||||
options: {
|
||||
weight: 4,
|
||||
spaceWeight: 4,
|
||||
color: '#000000',
|
||||
spaceColor: '#ffffff',
|
||||
opacity: 1.0,
|
||||
spaceOpacity: 0.0
|
||||
},
|
||||
|
||||
_addShapes: function () {
|
||||
this._stripe = new L.PatternPath({
|
||||
stroke: true,
|
||||
weight: this.options.weight,
|
||||
color: this.options.color,
|
||||
opacity: this.options.opacity
|
||||
});
|
||||
|
||||
this._space = new L.PatternPath({
|
||||
stroke: true,
|
||||
weight: this.options.spaceWeight,
|
||||
color: this.options.spaceColor,
|
||||
opacity: this.options.spaceOpacity
|
||||
});
|
||||
|
||||
this.addShape(this._stripe);
|
||||
this.addShape(this._space);
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
this._stripe.options.d = 'M0 ' + this._stripe.options.weight / 2 + ' H ' + this.options.width;
|
||||
this._space.options.d = 'M0 ' + (this._stripe.options.weight + this._space.options.weight / 2) + ' H ' + this.options.width;
|
||||
},
|
||||
|
||||
setStyle: L.Pattern.prototype.setStyle
|
||||
});
|
||||
|
||||
L.stripePattern = function (options) {
|
||||
return new L.StripePattern(options);
|
||||
};
|
||||
|
||||
/*
|
||||
* L.PatternShape is the base class that is used to define the shapes in Patterns.
|
||||
*/
|
||||
|
||||
L.PatternShape = L.Class.extend({
|
||||
|
||||
options: {
|
||||
stroke: true,
|
||||
color: '#3388ff',
|
||||
weight: 3,
|
||||
opacity: 1,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
// dashArray: null
|
||||
// dashOffset: null
|
||||
|
||||
// fill: false
|
||||
// fillColor: same as color by default
|
||||
fillOpacity: 0.2,
|
||||
fillRule: 'evenodd',
|
||||
// fillPattern: L.Pattern
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
L.setOptions(this, options);
|
||||
},
|
||||
|
||||
// Called when the parent Pattern get's added to the map,
|
||||
// or when added to a Pattern that is already on the map.
|
||||
onAdd: function (pattern) {
|
||||
this._pattern = pattern;
|
||||
if (this._pattern._dom) {
|
||||
this._initDom(); // This function is implemented by it's children.
|
||||
this._addDom();
|
||||
}
|
||||
},
|
||||
|
||||
addTo: function (pattern) {
|
||||
pattern.addShape(this);
|
||||
return this;
|
||||
},
|
||||
|
||||
redraw: function () {
|
||||
if (this._pattern) {
|
||||
this._updateShape(); // This function is implemented by it's children.
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setStyle: function (style) {
|
||||
L.setOptions(this, style);
|
||||
if (this._pattern) {
|
||||
this._updateStyle();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setShape: function (shape) {
|
||||
this.options = L.extend({}, this.options, shape);
|
||||
this._updateShape();
|
||||
},
|
||||
});
|
||||
|
||||
L.Pattern.include({
|
||||
addShape: function (shape) {
|
||||
var id = L.stamp(shape);
|
||||
if (this._shapes[id]) { return shape; }
|
||||
this._shapes[id] = shape;
|
||||
shape.onAdd(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
L.PatternShape.SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
L.PatternShape = L.PatternShape.extend({
|
||||
_createElement: function (name) {
|
||||
return document.createElementNS(L.PatternShape.SVG_NS, name);
|
||||
},
|
||||
|
||||
_initDom: L.Util.falseFn,
|
||||
_updateShape: L.Util.falseFn,
|
||||
|
||||
_initDomElement: function (type) {
|
||||
this._dom = this._createElement(type);
|
||||
if (this.options.className) {
|
||||
L.DomUtil.addClass(this._dom, this.options.className);
|
||||
}
|
||||
this._updateStyle();
|
||||
},
|
||||
|
||||
_addDom: function () {
|
||||
this._pattern._dom.appendChild(this._dom);
|
||||
},
|
||||
|
||||
_updateStyle: function () {
|
||||
var dom = this._dom,
|
||||
options = this.options;
|
||||
|
||||
if (!dom) { return; }
|
||||
|
||||
if (options.stroke) {
|
||||
dom.setAttribute('stroke', options.color);
|
||||
dom.setAttribute('stroke-opacity', options.opacity);
|
||||
dom.setAttribute('stroke-width', options.weight);
|
||||
dom.setAttribute('stroke-linecap', options.lineCap);
|
||||
dom.setAttribute('stroke-linejoin', options.lineJoin);
|
||||
|
||||
if (options.dashArray) {
|
||||
dom.setAttribute('stroke-dasharray', options.dashArray);
|
||||
} else {
|
||||
dom.removeAttribute('stroke-dasharray');
|
||||
}
|
||||
|
||||
if (options.dashOffset) {
|
||||
dom.setAttribute('stroke-dashoffset', options.dashOffset);
|
||||
} else {
|
||||
dom.removeAttribute('stroke-dashoffset');
|
||||
}
|
||||
} else {
|
||||
dom.setAttribute('stroke', 'none');
|
||||
}
|
||||
|
||||
if (options.fill) {
|
||||
if (options.fillPattern) {
|
||||
dom.setAttribute('fill', 'url(#' + L.stamp(options.fillPattern) + ")");
|
||||
}
|
||||
else {
|
||||
dom.setAttribute('fill', options.fillColor || options.color);
|
||||
}
|
||||
dom.setAttribute('fill-opacity', options.fillOpacity);
|
||||
dom.setAttribute('fill-rule', options.fillRule || 'evenodd');
|
||||
} else {
|
||||
dom.setAttribute('fill', 'none');
|
||||
}
|
||||
|
||||
dom.setAttribute('pointer-events', options.pointerEvents || (options.interactive ? 'visiblePainted' : 'none'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* L.PatternPath is the implementation of PatternShape for adding Paths
|
||||
*/
|
||||
|
||||
L.PatternPath = L.PatternShape.extend({
|
||||
// options: {
|
||||
// d: <svg path code>
|
||||
// },
|
||||
|
||||
_initDom: function () {
|
||||
this._initDomElement('path');
|
||||
},
|
||||
|
||||
_updateShape: function () {
|
||||
if (!this._dom) { return; }
|
||||
this._dom.setAttribute('d', this.options.d);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* L.PatternCircle is the implementation of PatternShape for adding Circles
|
||||
*/
|
||||
|
||||
L.PatternCircle = L.PatternShape.extend({
|
||||
options: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
radius: 0
|
||||
},
|
||||
|
||||
_initDom: function () {
|
||||
this._initDomElement('circle');
|
||||
},
|
||||
|
||||
_updateShape: function () {
|
||||
if (!this._dom) { return; }
|
||||
this._dom.setAttribute('cx', this.options.x);
|
||||
this._dom.setAttribute('cy', this.options.y);
|
||||
this._dom.setAttribute('r', this.options.radius);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* L.PatternRect is the implementation of PatternShape for adding Rectangles
|
||||
*/
|
||||
|
||||
L.PatternRect = L.PatternShape.extend({
|
||||
options: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
// rx: x radius for rounded corners
|
||||
// ry: y radius for rounded corners
|
||||
},
|
||||
|
||||
_initDom: function () {
|
||||
this._initDomElement('rect');
|
||||
},
|
||||
|
||||
_updateShape: function () {
|
||||
if (!this._dom) { return; }
|
||||
this._dom.setAttribute('x', this.options.x);
|
||||
this._dom.setAttribute('y', this.options.y);
|
||||
this._dom.setAttribute('width', this.options.width);
|
||||
this._dom.setAttribute('height', this.options.height);
|
||||
if (this.options.rx) { this._dom.setAttribute('rx', this.options.rx); }
|
||||
if (this.options.ry) { this._dom.setAttribute('ry', this.options.ry); }
|
||||
}
|
||||
});
|
||||
|
||||
}(window, document));
|
||||
@@ -0,0 +1,7 @@
|
||||
export function actionRequest(type){
|
||||
return type + '_REQUEST'
|
||||
}
|
||||
|
||||
export function actionFailure(type){
|
||||
return type + '_FAILURE'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {mobileDetectionScreenSize} from '../config'
|
||||
|
||||
export const isLandscapeMedia = matchMedia('(orientation: landscape)')
|
||||
|
||||
function matchMedia(query) {
|
||||
return window.matchMedia(query)
|
||||
}
|
||||
|
||||
export function isMobile() {
|
||||
const landscape = isLandscape()
|
||||
const mobileWidth = landscape
|
||||
? mobileDetectionScreenSize[0]
|
||||
: mobileDetectionScreenSize[1]
|
||||
|
||||
const isMobileMedia = matchMedia(`(max-width: ${mobileWidth}px)`)
|
||||
return isMobileMedia.matches
|
||||
}
|
||||
|
||||
export function isLandscape(){
|
||||
return isLandscapeMedia.matches
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* f([x, y, z, ...], [m, n, k, ...]) -> [[x, m], [y, n], [z, k], ...]
|
||||
* @param list1
|
||||
* @param list2
|
||||
*/
|
||||
export function zip(list1, list2) {
|
||||
const [small, other] = list1.length <= list2.length
|
||||
? [list1, list2]
|
||||
: [list2, list1]
|
||||
return small.map((s, i) => [s, other[i]])
|
||||
}
|
||||
|
||||
/**
|
||||
* f(g, [x, y, z, ...], [m, n, k, ...]) -> [[x, m], [y, n], [z, k], ...] based on G
|
||||
* @param list1
|
||||
* @param list2
|
||||
*/
|
||||
export function zipBy (key, list1, list2) {
|
||||
const [small, other] = list1.length <= list2.length
|
||||
? [list1, list2]
|
||||
: [list2, list1]
|
||||
|
||||
return small.map(s => {
|
||||
const k = key(s)
|
||||
const o = other.find(i => key(i) === k)
|
||||
return [s, o]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function arraysIsEqual(list1, list2){
|
||||
if (list1.length !== list2.length) return false
|
||||
|
||||
const booleans = zip(list1, list2)
|
||||
.map((a, b) => a === b)
|
||||
return all(booleans)
|
||||
}
|
||||
|
||||
export function all(list){
|
||||
return list
|
||||
.map(Boolean)
|
||||
.reduce((acc, i) => acc && i, true)
|
||||
}
|
||||
|
||||
export function any(list){
|
||||
return list
|
||||
.map(Boolean)
|
||||
.reduce((acc, i) => acc || i, false)
|
||||
}
|
||||
|
||||
export function exist(value){
|
||||
return all([
|
||||
value !== null,
|
||||
value !== undefined,
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* f(g) -> m(x) -> g(x) -> x
|
||||
*
|
||||
* @param fn
|
||||
* @returns {function(*)}
|
||||
*/
|
||||
export function pass(fn) {
|
||||
return x => {
|
||||
fn(x)
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* f(n) -> g(x) -> {[n]: x}
|
||||
*
|
||||
* @param name
|
||||
* @returns {function(*): {}}
|
||||
*/
|
||||
export function pack(name) {
|
||||
//return value => {[name]: value}
|
||||
|
||||
return value => ({[name]: value})
|
||||
}
|
||||
|
||||
/**
|
||||
* f(g, m, ...) -> g . m . ...
|
||||
*
|
||||
* @param functions
|
||||
* @returns {function(*=): *}
|
||||
*/
|
||||
export function compose(...functions) {
|
||||
const fs = functions.reverse()
|
||||
return (...value) => fs.reduce((acc, fn) => fn(...acc), value)
|
||||
}
|
||||
|
||||
/**
|
||||
* f(t, g) -> g(x) after t milliseconds
|
||||
*
|
||||
* @param ms
|
||||
* @param callback
|
||||
* @returns {function(...[*])}
|
||||
*/
|
||||
export function debounce(ms, callback) {
|
||||
let id = 0
|
||||
return (...args) => {
|
||||
clearTimeout(id)
|
||||
id = setTimeout(callback, ms, ...args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Leaflet from 'leaflet'
|
||||
|
||||
export function getBoundsOf(feature) {
|
||||
return Leaflet
|
||||
.geoJSON(feature)
|
||||
.getBounds()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {actionRequest, actionFailure} from './action'
|
||||
|
||||
export default function promiseMiddleware() {
|
||||
return next => action => {
|
||||
const {async, type, ...rest} = action
|
||||
if (!async) return next(action)
|
||||
|
||||
next({...rest, type: actionRequest(type)})
|
||||
return async
|
||||
.then(res => {
|
||||
next({...rest, ...res, error: null, type})
|
||||
return true
|
||||
})
|
||||
.catch(error => {
|
||||
next({...rest, error, type: actionFailure(type)})
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default function createEffects(effects) {
|
||||
return store => next => action => {
|
||||
next(action)
|
||||
effects.forEach(effect => effect(store.getState(), action, store.dispatch))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function sleep(ms = 0) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export function getVariable(name, element = document.documentElement) {
|
||||
const value = getComputedStyle(element).getPropertyValue(name)
|
||||
return parseValue(value)
|
||||
}
|
||||
|
||||
function parseValue(value) {
|
||||
const values = /\s*(\d+)(%|px)\s*/.exec(value)
|
||||
if (values) {
|
||||
const v = Number(values[1])
|
||||
// const v = values[1]
|
||||
const w = values[2]
|
||||
if (w === '%') return v / 100
|
||||
return v
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {combineReducers} from 'redux'
|
||||
import {routerReducer} from 'react-router-redux'
|
||||
// import app from '../features/App/duck'
|
||||
|
||||
export default combineReducers({
|
||||
// app,
|
||||
routing: routerReducer
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import {createStore, applyMiddleware, compose} from 'redux'
|
||||
// import thunk from 'redux-thunk'
|
||||
// import async from '../lib/promiseMiddleware'
|
||||
// import createEffects from '../lib/sideEffectsMiddleware'
|
||||
|
||||
// import {routerMiddleware} from 'react-router-redux'
|
||||
// import {browserHistory} from 'react-router'
|
||||
|
||||
export function create(reducer, init, enhancers = [], effects = []) {
|
||||
// const browser = routerMiddleware(browserHistory)
|
||||
// const es = createEffects(effects)
|
||||
// const enhancer = compose(applyMiddleware(browser, async, thunk, es), ...enhancers)
|
||||
const enhancer = compose(applyMiddleware(), ...enhancers)
|
||||
return createStore(reducer, init, enhancer)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'babel-polyfill'
|
||||
|
||||
import {dbUri, port, name} from './config'
|
||||
import {connect} from './core/db'
|
||||
import server from './server'
|
||||
|
||||
+14
-1
@@ -11,7 +11,7 @@ const isEqualPate = require('../lib/url').isEqualPath
|
||||
const {compose, any} = require('../lib/common')
|
||||
|
||||
const menuModel = require('../models/menu').default
|
||||
const {viewMain} = require('../config')
|
||||
const {viewMain, viewEdit} = require('../config')
|
||||
|
||||
const defaultOptions = {
|
||||
commentsEnabled: false,
|
||||
@@ -105,4 +105,17 @@ async function render(path, data, meta, options = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
async function renderEdit() {
|
||||
const renderOptions = {
|
||||
...defaultOptions,
|
||||
templateFile: viewEdit,
|
||||
}
|
||||
|
||||
const source = await readFile(renderOptions.templateFile, 'utf-8')
|
||||
const template = handlebars.compile(source)
|
||||
|
||||
return template({})
|
||||
}
|
||||
|
||||
exports.render = render
|
||||
exports.renderEdit = renderEdit
|
||||
@@ -0,0 +1,10 @@
|
||||
const {get} = require('koa-route')
|
||||
const {renderEdit} = require('../lib/render')
|
||||
|
||||
function getEdit() {
|
||||
return get('/edit', async ctx => {
|
||||
ctx.body = await renderEdit()
|
||||
})
|
||||
}
|
||||
|
||||
exports.getEdit = getEdit
|
||||
+5
-6
@@ -10,15 +10,14 @@ export function queryObject() {
|
||||
const processors = [toTrue, toFalse]
|
||||
|
||||
return async(ctx, next) => {
|
||||
let query = ctx.query
|
||||
ctx.query = Object
|
||||
.keys(query)
|
||||
.keys(ctx.query)
|
||||
.reduce((query, key) => assign(
|
||||
query,
|
||||
key,
|
||||
processors
|
||||
.reduce((value, fn) => fn(value), query[key])
|
||||
), query)
|
||||
), ctx.query)
|
||||
|
||||
await next()
|
||||
}
|
||||
@@ -26,9 +25,9 @@ export function queryObject() {
|
||||
|
||||
export function accepts(routes) {
|
||||
return async function (ctx) {
|
||||
let request = ctx.request
|
||||
let types = Object.keys(routes)
|
||||
.filter(type => request.accepts(type))
|
||||
const types = Object
|
||||
.keys(routes)
|
||||
.filter(type => ctx.request.accepts(type))
|
||||
|
||||
if (types.length) {
|
||||
let fn = routes[types[0]]
|
||||
|
||||
@@ -27,6 +27,7 @@ const document = require('./routes/document')
|
||||
const schedule = require('./routes/schedule')
|
||||
const pages = require('./routes/pages')
|
||||
const sitemap = require('./routes/sitemap')
|
||||
const edit = require('./routes/edit')
|
||||
|
||||
const dirPublic = path.join(__dirname, '../public')
|
||||
|
||||
@@ -65,6 +66,7 @@ export default function (config) {
|
||||
app.use(error.notFound(view404))
|
||||
app.use(sitemap())
|
||||
|
||||
// app.use(edit.getEdit())
|
||||
app.use(home.getHome(config.articlesPerPage))
|
||||
app.use(gallery.getGallery())
|
||||
app.use(article.getArticles(config.articlesPerPage))
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "variables";
|
||||
|
||||
.body-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: white;
|
||||
|
||||
font-family: $text-font;
|
||||
font-family: var(--text-font);
|
||||
font-size: var(--font-size-default);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.Article {
|
||||
--article-line-height: 1.75em;
|
||||
--article-quote-padding: var(--half-margin);
|
||||
@@ -8,7 +6,7 @@
|
||||
|
||||
hyphens: auto;
|
||||
|
||||
margin-bottom: $post-margin;
|
||||
margin-bottom: var(--post-margin);
|
||||
|
||||
&-Head {
|
||||
margin-bottom: 1.25em;
|
||||
@@ -29,8 +27,8 @@
|
||||
--text-margin: 10px;
|
||||
|
||||
ul, ol {
|
||||
margin-left: $half-margin;
|
||||
margin-bottom: $single-margin;
|
||||
margin-left: var(--half-margin);
|
||||
margin-bottom: var(--single-margin);
|
||||
|
||||
li {
|
||||
margin-bottom: var(--half-margin);
|
||||
@@ -54,8 +52,8 @@
|
||||
blockquote {
|
||||
background-color: hsl(215, 10%, 96%);
|
||||
padding: var(--article-quote-padding);
|
||||
margin: $single-margin 0;
|
||||
font-family: $title-font;
|
||||
margin: var(--single-margin) 0;
|
||||
font-family: var(--title-font);
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
font-size: var(--article-quote-font-size);
|
||||
@@ -63,13 +61,13 @@
|
||||
border-radius: var(--block-border-radius);
|
||||
|
||||
cite {
|
||||
margin-top: $single-margin;
|
||||
margin-top: var(--single-margin);
|
||||
display: block;
|
||||
color: #aaa;
|
||||
font-size: initial;
|
||||
font-family: $text-font;
|
||||
font-family: var(--text-font);
|
||||
text-align: right;
|
||||
margin-right: $single-margin;
|
||||
margin-right: var(--single-margin);
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -93,7 +91,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.Article {
|
||||
--article-line-height: 1.5em;
|
||||
--article-quote-padding: .5em;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.ArticleCard {
|
||||
--article-card-width: 30%;
|
||||
--article-card-image-height: 15em;
|
||||
@@ -78,7 +76,7 @@
|
||||
|
||||
// Tablet
|
||||
|
||||
@media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) {
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
.ArticleCard {
|
||||
--article-card-image-height: 12em;
|
||||
}
|
||||
@@ -90,7 +88,7 @@
|
||||
|
||||
// Mobile
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.ArticleCard {
|
||||
--article-card-width: 100%;
|
||||
--article-card-margin: var(--single-margin) 0;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.HudozkaTitle {
|
||||
--padding: var(--double-margin) 0;
|
||||
|
||||
@@ -17,7 +15,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.HudozkaTitle {
|
||||
--padding: 0 0 var(--double-margin);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.PersonCard {
|
||||
--person-card-width: 30%;
|
||||
--person-card-height: 24em;
|
||||
@@ -78,7 +76,7 @@
|
||||
|
||||
// Tablet
|
||||
|
||||
@media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) {
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
.PersonCard {
|
||||
--person-card-height: 20em;
|
||||
}
|
||||
@@ -90,7 +88,7 @@
|
||||
|
||||
// Mobile
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.PersonCard {
|
||||
--person-card-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "../variables";
|
||||
|
||||
.accessibility-control {
|
||||
width: 100%;
|
||||
|
||||
padding: $single-margin;
|
||||
padding: var(--single-margin);
|
||||
|
||||
span {
|
||||
margin-right: $single-margin;
|
||||
margin-right: var(--single-margin);
|
||||
}
|
||||
|
||||
span.active {
|
||||
@@ -14,12 +12,12 @@
|
||||
}
|
||||
|
||||
&__block {
|
||||
margin-bottom: $half-margin;
|
||||
margin-bottom: var(--half-margin);
|
||||
}
|
||||
|
||||
ul li {
|
||||
display: inline;
|
||||
margin-right: $single-margin;
|
||||
margin-right: var(--single-margin);
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.album-collection{
|
||||
&__body{
|
||||
display: flex;
|
||||
@@ -8,11 +6,11 @@
|
||||
justify-content: flex-start;
|
||||
align-items:center;
|
||||
|
||||
margin: 0 0 $double-margin;
|
||||
margin: 0 0 var(--double-margin);
|
||||
}
|
||||
|
||||
&__title{
|
||||
text-align: center;
|
||||
margin: $single-margin 0;
|
||||
margin: var(--single-margin )0;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
@import "../variables";
|
||||
|
||||
.album {
|
||||
margin-bottom: $double-margin;
|
||||
margin-bottom: var(--double-margin);
|
||||
|
||||
&__head {
|
||||
margin-bottom: 1.25em;
|
||||
|
||||
h3 {
|
||||
font-size: $biggest-font-size;
|
||||
font-size: var(--biggest-font-size);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -17,7 +15,7 @@
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-color;
|
||||
color: var(--dark-color);
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +26,7 @@
|
||||
max-height: 40em;
|
||||
|
||||
display: block;
|
||||
margin: 1.25em auto $small-margin;
|
||||
margin: 1.25em auto var(--small-margin);
|
||||
}
|
||||
|
||||
p{
|
||||
@@ -37,7 +35,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.album {
|
||||
&__body {
|
||||
img {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
@import "../variables";
|
||||
|
||||
$font-size: 0.75em;
|
||||
$item-active-color: $link-color;
|
||||
$item-inactive-color: $inactive-link-color;
|
||||
$separator: '⟶';
|
||||
$mobile-separator: '>';
|
||||
|
||||
breadcrumbs{
|
||||
--font-size: 0.75em;
|
||||
--item-active-color: var(--link-color);
|
||||
--item-inactive-color: var(--inactive-link-color);
|
||||
--separator: '⟶';
|
||||
--mobile-separator: '>';
|
||||
|
||||
padding: .35em 0 0;
|
||||
color: $item-inactive-color;
|
||||
color: var(--item-inactive-color);
|
||||
|
||||
a {
|
||||
color: $item-active-color;
|
||||
color: var(--item-active-color);
|
||||
}
|
||||
|
||||
ul{
|
||||
@@ -21,22 +19,22 @@ breadcrumbs{
|
||||
display: inline-block;
|
||||
|
||||
&:not(:first-child):before{
|
||||
content: $separator;
|
||||
content: var(--separator);
|
||||
margin-left: .4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
@media screen and (max-width: var(--mobile-width)) {
|
||||
breadcrumbs{
|
||||
margin: 0;
|
||||
font-size: $font-size;
|
||||
font-size: var(--font-size);
|
||||
|
||||
ul{
|
||||
li {
|
||||
&:not(:first-child):before{
|
||||
content: $mobile-separator;
|
||||
content: var(--mobile-separator);
|
||||
margin-left: .2em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.document-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -20,7 +18,7 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: $white-shadow;
|
||||
box-shadow: var(--white-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +37,11 @@
|
||||
.document-row__file-info {
|
||||
width: 25%;
|
||||
flex-grow: 1;
|
||||
font-size: $smaller-font-size;
|
||||
font-size: var(--smaller-font-size);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.document-row__file {
|
||||
margin-left: var(--half-margin);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
@import "../variables";
|
||||
|
||||
.document {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin-bottom: $double-margin;
|
||||
margin-bottom: var(--double-margin);
|
||||
}
|
||||
|
||||
.document__image{
|
||||
@@ -13,7 +11,7 @@
|
||||
//width: 20em;
|
||||
//height: 20em;
|
||||
|
||||
margin-bottom: $single-margin;
|
||||
margin-bottom: var(--single-margin);
|
||||
|
||||
//display: flex;
|
||||
//justify-content: center;
|
||||
@@ -23,7 +21,7 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: $white-shadow;
|
||||
box-shadow: var(--white-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +37,11 @@
|
||||
}
|
||||
|
||||
.document__file-info {
|
||||
font-size: $smaller-font-size;
|
||||
font-size: var(--smaller-font-size);
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.document {
|
||||
margin: 0 $single-margin/2 $single-margin;
|
||||
margin: 0 var(--single-margin) var(--single-margin);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
@import "../variables";
|
||||
|
||||
.documents-container {
|
||||
margin-bottom: $post-margin;
|
||||
margin-bottom: var(--post-margin);
|
||||
|
||||
&__head {
|
||||
margin-bottom: $double-margin;
|
||||
margin-bottom: var(--double-margin);
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -14,7 +12,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.documents-container {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "../variables";
|
||||
|
||||
.footer-links {
|
||||
&__header {
|
||||
margin-bottom: $single-margin;
|
||||
margin-bottom: var(--single-margin);
|
||||
}
|
||||
|
||||
&__link {
|
||||
margin-bottom: $half-margin;
|
||||
margin-bottom: var(--half-margin);
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
@import "../variables";
|
||||
|
||||
$img-max-width: 500px;
|
||||
$double-margin-half: $double-margin / 2;
|
||||
$percentage: 25%;
|
||||
|
||||
.gallery-item {
|
||||
box-sizing: padding-box;
|
||||
padding: 0;
|
||||
|
||||
margin: 0 $single-margin $double-margin;
|
||||
margin: 0 var(--single-margin) var(--double-margin);
|
||||
|
||||
a.invisible {
|
||||
border-bottom: none;
|
||||
@@ -16,8 +10,8 @@ $percentage: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
@media screen and (max-width: var(--mobile-width)) {
|
||||
.gallery-item {
|
||||
margin: 0 0 $double-margin;
|
||||
margin: 0 0 var(--double-margin);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
@import "../variables";
|
||||
|
||||
.main-menu {
|
||||
font-size: $normal-font-size;
|
||||
font-size: var(--normal-font-size);
|
||||
|
||||
list-style: none;
|
||||
|
||||
@@ -35,45 +33,45 @@
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-color;
|
||||
border-color: lighten($dark-color, 60%);
|
||||
color: var(--dark-color);
|
||||
border-color: var(--dark-color);
|
||||
|
||||
&:hover {
|
||||
border-color: lighten($dark-color, 30%);
|
||||
border-color: var(--dark-color);
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: bold;
|
||||
border-top: $menu-colored-mark-thickness solid $light-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--light-color);
|
||||
|
||||
&.blue {
|
||||
border-top: $menu-colored-mark-thickness solid $blue-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--blue-color);
|
||||
}
|
||||
|
||||
&.orange {
|
||||
border-top: $menu-colored-mark-thickness solid $orange-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--orange-color);
|
||||
}
|
||||
|
||||
&.green {
|
||||
border-top: $menu-colored-mark-thickness solid $green-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--green-color);
|
||||
}
|
||||
|
||||
&.yellow {
|
||||
border-top: $menu-colored-mark-thickness solid $yellow-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--yellow-color);
|
||||
}
|
||||
|
||||
&.pink {
|
||||
border-top: $menu-colored-mark-thickness solid $pink-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--pink-color);
|
||||
}
|
||||
|
||||
&.red {
|
||||
border-top: $menu-colored-mark-thickness solid $red-color;
|
||||
border-top: var(--menu-colored-mark-thickness) solid var(--red-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.main-menu{
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
@@ -12,14 +10,14 @@
|
||||
}
|
||||
|
||||
.navigation__right {
|
||||
color: $dark-color;
|
||||
font-size: $normal-font-size;
|
||||
color: var(--dark-color);
|
||||
font-size: var(--normal-font-size);
|
||||
margin-right: 1em;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.navigation {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -29,7 +27,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) {
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
@media screen and (orientation: portrait) {
|
||||
.navigation__right {
|
||||
display: none;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "../variables";
|
||||
|
||||
.page-collective {
|
||||
&.ng-leave {
|
||||
animation: slideOutLeft $short-duration both ease-in;
|
||||
animation: slideOutLeft var(--short-duration) both ease-in;
|
||||
}
|
||||
|
||||
&.ng-enter {
|
||||
animation: slideInRight $short-duration both ease-in;
|
||||
animation: slideInRight var(--short-duration) both ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +14,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.page-collective {
|
||||
&__collective-image {
|
||||
display: none;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "../variables";
|
||||
|
||||
.page-schedule {
|
||||
&.ng-leave {
|
||||
transform-origin: 0% 0%;
|
||||
animation: rotateFall $short-duration both ease-in;
|
||||
animation: rotateFall var(--short-duration) both ease-in;
|
||||
}
|
||||
|
||||
&.ng-enter {
|
||||
animation: scaleUp $short-duration both ease-in;
|
||||
animation: scaleUp var(--short-duration) both ease-in;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
@import "../variables";
|
||||
|
||||
$current-week-day-color: $blue-color;
|
||||
|
||||
$cell-width: 9em;
|
||||
$cell-tablet-width: 7.5em;
|
||||
|
||||
$group-cell-width: 4em;
|
||||
$group-cell-tablet-width: 3em;
|
||||
|
||||
$current-week-day-color: $blue-color;
|
||||
$row-padding: $single-margin 0;
|
||||
$current-day-column-border-thickness: 3px;
|
||||
|
||||
.schedule-table {
|
||||
margin-bottom: $double-margin;
|
||||
--current-week-day-color: var(--blue-color);
|
||||
|
||||
--cell-width: 9em;
|
||||
--cell-tablet-width: 7.5em;
|
||||
|
||||
--group-cell-width: 4em;
|
||||
--group-cell-tablet-width: 3em;
|
||||
|
||||
--row-padding: var(--single-margin) 0;
|
||||
--current-day-column-border-thickness: 3px;
|
||||
|
||||
margin-bottom: var(--double-margin);
|
||||
|
||||
max-width: 70em;
|
||||
overflow-x: auto;
|
||||
|
||||
a{
|
||||
color: $dark-color;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.lesson {
|
||||
margin-bottom: $half-margin;
|
||||
margin-bottom: var(--half-margin);
|
||||
}
|
||||
|
||||
.teacher {
|
||||
@@ -31,8 +28,8 @@ $current-day-column-border-thickness: 3px;
|
||||
}
|
||||
|
||||
.current {
|
||||
border-left: $current-day-column-border-thickness solid;
|
||||
border-right: $current-day-column-border-thickness solid;
|
||||
border-left: var(--current-day-column-border-thickness) solid;
|
||||
border-right: var(--current-day-column-border-thickness) solid;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -46,8 +43,8 @@ $current-day-column-border-thickness: 3px;
|
||||
}
|
||||
|
||||
&__body-group{
|
||||
min-width: $group-cell-width;
|
||||
padding: $row-padding;
|
||||
min-width: var(--group-cell-width);
|
||||
padding: var(--row-padding);
|
||||
|
||||
text-align: center;
|
||||
|
||||
@@ -58,14 +55,14 @@ $current-day-column-border-thickness: 3px;
|
||||
}
|
||||
|
||||
&__head-record {
|
||||
min-width: $cell-width;
|
||||
max-width: $cell-width;
|
||||
padding: $row-padding;
|
||||
min-width: var(--cell-width);
|
||||
max-width: var(--cell-width);
|
||||
padding: var(--row-padding);
|
||||
|
||||
text-align: center;
|
||||
|
||||
& > .group {
|
||||
min-width: $group-cell-width;
|
||||
min-width: var(--group-cell-width);
|
||||
}
|
||||
|
||||
.date {
|
||||
@@ -75,19 +72,19 @@ $current-day-column-border-thickness: 3px;
|
||||
}
|
||||
|
||||
&__record{
|
||||
min-width: $cell-width;
|
||||
max-width: $cell-width;
|
||||
padding: $row-padding;
|
||||
min-width: var(--cell-width);
|
||||
max-width: var(--cell-width);
|
||||
padding: var(--row-padding);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__course{
|
||||
margin-bottom: $half-margin;
|
||||
margin-bottom: var(--half-margin);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) {
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
.schedule-table {
|
||||
min-width: 900px;
|
||||
|
||||
@@ -103,16 +100,16 @@ $current-day-column-border-thickness: 3px;
|
||||
|
||||
&__head{
|
||||
& > div{
|
||||
min-width: $cell-tablet-width;
|
||||
min-width: var(--cell-tablet-width);
|
||||
}
|
||||
|
||||
& > .group{
|
||||
min-width: $group-cell-tablet-width;
|
||||
min-width: var(--group-cell-tablet-width);
|
||||
}
|
||||
}
|
||||
|
||||
&__body-group{
|
||||
min-width: $group-cell-tablet-width;
|
||||
min-width: var(--group-cell-tablet-width);
|
||||
|
||||
p.name {
|
||||
font-size: 1em;
|
||||
@@ -120,7 +117,7 @@ $current-day-column-border-thickness: 3px;
|
||||
}
|
||||
|
||||
&__record{
|
||||
min-width: $cell-tablet-width;
|
||||
min-width: var(--cell-tablet-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
@import "../variables";
|
||||
|
||||
.timeline__record {
|
||||
margin: 0 0 $post-margin;
|
||||
margin: 0 0 var(--post-margin);
|
||||
|
||||
.date {
|
||||
color: $light-color;
|
||||
color: var(--light-color);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
@import "../variables";
|
||||
|
||||
.timeline{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: $double-margin auto;
|
||||
margin: var(--double-margin) auto;
|
||||
}
|
||||
|
||||
.timeline__footer{
|
||||
@@ -18,12 +16,12 @@
|
||||
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
border: 2px solid $dark-color;
|
||||
border: 2px solid var(--dark-color);
|
||||
padding: 0 10px;
|
||||
color: $dark-color;
|
||||
background: $lighter-color;
|
||||
color: var(--dark-color);
|
||||
background: var(--lighter-color);
|
||||
|
||||
font-size: $normal-font-size;
|
||||
font-size: var(--normal-font-size);
|
||||
|
||||
&:hover {
|
||||
background: gainsboro;
|
||||
|
||||
+11
-14
@@ -1,26 +1,24 @@
|
||||
@import "variables";
|
||||
|
||||
.content {
|
||||
margin: 0 auto 1em;
|
||||
|
||||
&.content_full{
|
||||
width: $content-full;
|
||||
width: var(--content-full);
|
||||
}
|
||||
|
||||
&.content_wide{
|
||||
width: $content-wide;
|
||||
width: var(--content-wide);
|
||||
}
|
||||
|
||||
&.content_semi-wide{
|
||||
width: $content-semi-wide;
|
||||
width: var(--content-semi-wide);
|
||||
}
|
||||
|
||||
&.content_thin{
|
||||
width: $content-thin;
|
||||
width: var(--content-thin);
|
||||
}
|
||||
|
||||
&.content--padding-top--menu{
|
||||
padding-top: $menu-thickness;
|
||||
padding-top: var(--menu-thickness);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,21 +40,20 @@
|
||||
animation: smallSlideInFromTop 350ms ease-out;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
$content-mobile: 95%;
|
||||
@media screen and (max-width: var(--mobile-width)) {
|
||||
--content-mobile: 95%;
|
||||
|
||||
.content {
|
||||
&.content_wide{
|
||||
width: $content-mobile;
|
||||
width: var(--content-mobile);
|
||||
}
|
||||
|
||||
&.content_semi-wide{
|
||||
width: $content-mobile;
|
||||
width: var(--content-mobile);
|
||||
}
|
||||
|
||||
&.content_thin{
|
||||
width: $content-mobile;
|
||||
width: var(--content-mobile);
|
||||
}
|
||||
|
||||
&.content--padding-top--menu{
|
||||
@@ -65,7 +62,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet-min-width) and (max-width: $tablet-max-width) {
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
$thin-value: 70%;
|
||||
$wide-value: 95%;
|
||||
$semi-wide-value: 80%;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "variables";
|
||||
|
||||
footer {
|
||||
padding: 0 $half-margin $half-margin;
|
||||
padding: 0 var(--half-margin) var(--half-margin);
|
||||
position: relative;
|
||||
|
||||
.footer-info {
|
||||
@@ -9,19 +9,19 @@ footer {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: $double-margin $single-margin 0;
|
||||
margin-bottom: $double-margin;
|
||||
padding: var(--double-margin) var(--single-margin) 0;
|
||||
margin-bottom: var(--double-margin);
|
||||
}
|
||||
|
||||
.contacts {
|
||||
margin: $double-margin 0 $half-margin;
|
||||
margin: var(--double-margin) 0 var(--half-margin);
|
||||
|
||||
& > div {
|
||||
padding: 0 $single-margin;
|
||||
padding: 0 var(--single-margin);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: $half-margin $single-margin;
|
||||
margin: var(--half-margin) var(--single-margin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-12
@@ -1,14 +1,12 @@
|
||||
@import "variables";
|
||||
|
||||
$hmenu-margin: 6px;
|
||||
$hmenu-icon-width: 32px;
|
||||
$hmenu-icon-height: 32px;
|
||||
|
||||
.no-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.HMenu {
|
||||
--hmenu-margin: 6px;
|
||||
--hmenu-icon-width: 32px;
|
||||
--hmenu-icon-height: 32px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@@ -39,7 +37,7 @@ $hmenu-icon-height: 32px;
|
||||
|
||||
.HMenu--folded.HMenu--open {
|
||||
li {
|
||||
margin: 0 0 $single-margin;
|
||||
margin: 0 0 var(--single-margin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +61,9 @@ $hmenu-icon-height: 32px;
|
||||
.HMenu__toggle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: $hmenu-icon-height;
|
||||
height: var(--hmenu-icon-height);
|
||||
|
||||
margin: $hmenu-margin;
|
||||
margin: var(--hmenu-margin);
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -73,8 +71,8 @@ $hmenu-icon-height: 32px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
|
||||
width: $hmenu-icon-width;
|
||||
height: $hmenu-icon-height;
|
||||
width: var(--hmenu-icon-width);
|
||||
height: var(--hmenu-icon-height);
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
@@ -157,7 +155,7 @@ $hmenu-icon-height: 32px;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.HMenu--folded .HMenu__item {
|
||||
max-width: 10em;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../variables";
|
||||
|
||||
.kazimir__image {
|
||||
--kazimir-caption-margin: 20px;
|
||||
|
||||
@@ -17,7 +15,7 @@
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 $single-margin;
|
||||
margin: 0 0 var(--single-margin);
|
||||
}
|
||||
|
||||
figcaption {
|
||||
@@ -31,7 +29,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-width) {
|
||||
@media screen and (max-width: var(--mobile-width)) {
|
||||
.kazimir__image {
|
||||
--kazimir-caption-margin: 10px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
@import "../variables";
|
||||
|
||||
.kazimir__video {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
position: relative;
|
||||
padding-bottom: $embed-video-ratio;
|
||||
padding-bottom: var(--embed-video-ratio);
|
||||
|
||||
margin-bottom: $single-margin;
|
||||
margin-bottom: var(--single-margin);
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "variables";
|
||||
|
||||
.main-image{
|
||||
display: none;
|
||||
|
||||
@@ -20,7 +18,7 @@
|
||||
//}
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.main-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
@import "variables";
|
||||
|
||||
.schedule-selector {
|
||||
text-align: center;
|
||||
margin-bottom: $single-margin;
|
||||
margin-bottom: var(--single-margin);
|
||||
|
||||
label{
|
||||
margin: 0 .5em;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@import "components/breadcrumbs";
|
||||
@import "components/timeline";
|
||||
@import "components/timeline-record";
|
||||
@import "components/timeline-record-instagram";
|
||||
//@import "components/timeline-record-instagram";
|
||||
@import "components/schedule-table";
|
||||
@import "components/footer-links";
|
||||
@import "components/Article";
|
||||
@@ -46,14 +46,14 @@
|
||||
// THEMES
|
||||
|
||||
@import "themes/theme-default";
|
||||
@import "themes/theme-accessibility-black-white";
|
||||
@import "themes/theme-accessibility-white-black";
|
||||
@import "themes/theme-accessibility-font-big";
|
||||
@import "themes/theme-accessibility-font-biggest";
|
||||
@import "themes/theme-accessibility-font-sans";
|
||||
@import "themes/theme-accessibility-font-serif";
|
||||
@import "themes/theme-accessibility-no-image";
|
||||
@import "themes/theme-accessibility-kerning-big";
|
||||
//@import "themes/theme-accessibility-black-white";
|
||||
//@import "themes/theme-accessibility-white-black";
|
||||
//@import "themes/theme-accessibility-font-big";
|
||||
//@import "themes/theme-accessibility-font-biggest";
|
||||
//@import "themes/theme-accessibility-font-sans";
|
||||
//@import "themes/theme-accessibility-font-serif";
|
||||
//@import "themes/theme-accessibility-no-image";
|
||||
//@import "themes/theme-accessibility-kerning-big";
|
||||
|
||||
@import "marker/MarkerTable";
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
@import "../variables";
|
||||
|
||||
.theme-default {
|
||||
h1, h2{
|
||||
font-family: $title-font;
|
||||
font-family: var(--title-font);
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-color;
|
||||
color: var(--dark-color);
|
||||
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid lighten($dark-color, 60%);
|
||||
border-bottom: 1px solid var(--link-color);
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid $dark-color;
|
||||
border-bottom: 2px solid var(--dark-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
@@ -34,7 +32,7 @@
|
||||
|
||||
.schedule-table {
|
||||
a{
|
||||
color: $dark-color;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.lesson {
|
||||
@@ -42,7 +40,7 @@
|
||||
}
|
||||
|
||||
.current {
|
||||
border-color: $current-week-day-color;
|
||||
border-color: var(--current-week-day-color);
|
||||
}
|
||||
|
||||
&__head{
|
||||
@@ -53,23 +51,23 @@
|
||||
|
||||
&__body-row{
|
||||
&:nth-child(even) {
|
||||
background-color: $schedule-row-color;
|
||||
background-color: var(--schedule-row-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
color: $light-color;
|
||||
background-color: $darker-color;
|
||||
color: var(--light-color);
|
||||
background-color: var(--darker-color);
|
||||
|
||||
font-size: $smaller-font-size;
|
||||
font-size: var(--smaller-font-size);
|
||||
|
||||
a {
|
||||
color: $light-color;
|
||||
border-bottom-color: darken($light-color, 50%);
|
||||
color: var(--light-color);
|
||||
border-bottom-color: var(--light-color);
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: $light-color;
|
||||
border-bottom-color: var(--light-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+57
-71
@@ -1,76 +1,39 @@
|
||||
//SIZE
|
||||
$mobile-width: 31.25em;
|
||||
$desktop-width: 59.99999em;
|
||||
|
||||
$tablet-min-width: 31.25em;
|
||||
$tablet-max-width: 64em;
|
||||
|
||||
$content-full: 100%;
|
||||
$content-wide: 90%;
|
||||
$content-semi-wide: 70%;
|
||||
$content-thin: 60%;
|
||||
|
||||
$embed-video-ratio: 56.25%;
|
||||
|
||||
//MARGINS
|
||||
$small-margin: .2em;
|
||||
$half-margin: .5em;
|
||||
$single-margin: 1.0em;
|
||||
$double-margin: 2.0em;
|
||||
$post-margin: 5.0em;
|
||||
|
||||
//COLOR
|
||||
$dark-color: hsl(0, 0%, 20%);
|
||||
$darker-color: hsl(0, 0%, 10%);
|
||||
$light-color: hsl(0, 0%, 80%);
|
||||
$lighter-color: hsl(0, 0%, 100%);
|
||||
|
||||
$blue-color: rgb(63, 156, 234);
|
||||
$orange-color: rgb(240, 160, 65);
|
||||
$green-color: rgb(92, 190, 64);
|
||||
$yellow-color: rgb(255, 238, 0);
|
||||
$pink-color: rgb(246, 85, 160);
|
||||
$magenta-color: rgb(214, 39, 246);
|
||||
$red-color: rgb(246, 27, 20);
|
||||
$gray-color: rgb(153, 154, 160);
|
||||
|
||||
$link-color: $blue-color;
|
||||
$inactive-link-color: $light-color;
|
||||
|
||||
$schedule-row-color: hsl(210, 55%, 90%);
|
||||
|
||||
:root {
|
||||
//SHADOW
|
||||
$white-shadow: 0 .1em 0.3em rgba(0, 0, 0, 0.2);
|
||||
--white-shadow: 0 .1em 0.3em rgba(0, 0, 0, 0.2);
|
||||
|
||||
//FONTS
|
||||
$title-font: "PT Serif", Georgia, serif;
|
||||
$text-font: "PT Sans", Arial, sans-serif;
|
||||
--title-font: "PT Serif", Georgia, serif;
|
||||
--text-font: "PT Sans", Arial, sans-serif;
|
||||
|
||||
$smaller-font-size: 0.9rem;
|
||||
$normal-font-size: 1.0rem;
|
||||
$bigger-font-size: 1.2rem;
|
||||
$big-font-size: 1.5rem;
|
||||
$biggest-font-size: 2.25rem;
|
||||
--smaller-font-size: 0.9rem;
|
||||
--normal-font-size: 1.0rem;
|
||||
--bigger-font-size: 1.2rem;
|
||||
--big-font-size: 1.5rem;
|
||||
--biggest-font-size: 2.25rem;
|
||||
|
||||
//MENU
|
||||
$menu-thickness: 2.5em;
|
||||
$menu-colored-mark-thickness: 4px;
|
||||
$menu-inactive-item-color: hsl(0, 0%, 50%);
|
||||
--menu-thickness: 2.5em;
|
||||
--menu-colored-mark-thickness: 4px;
|
||||
--menu-inactive-item-color: hsl(0, 0%, 50%);
|
||||
|
||||
//ANIMATION
|
||||
$short-duration: 0.3s;
|
||||
|
||||
//SIZE
|
||||
--mobile-width: 31.25em;
|
||||
--desktop-width: 59.99999em;
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
//FONTS
|
||||
$smaller-font-size: 0.9rem;
|
||||
$normal-font-size: 1.0rem;
|
||||
$bigger-font-size: 1.1rem;
|
||||
$big-font-size: 1.3rem;
|
||||
$biggest-font-size: 2.0rem;
|
||||
}
|
||||
--tablet-min-width: 31.25em;
|
||||
--tablet-max-width: 64em;
|
||||
|
||||
--content-full: 100%;
|
||||
--content-wide: 90%;
|
||||
--content-semi-wide: 70%;
|
||||
--content-thin: 60%;
|
||||
|
||||
--embed-video-ratio: 56.25%;
|
||||
|
||||
:root {
|
||||
--text-color: #303030;
|
||||
--text-color-second: #ccc;
|
||||
|
||||
@@ -83,24 +46,47 @@ $short-duration: 0.3s;
|
||||
--font-size-h3: 1.5rem;
|
||||
|
||||
//COLOR
|
||||
--dark-color: $dark-color;
|
||||
--darker-color: $darker-color;
|
||||
--light-color: $light-color;
|
||||
--lighter-color: $lighter-color;
|
||||
--dark-color: hsl(0, 0%, 20%);
|
||||
--darker-color: hsl(0, 0%, 10%);
|
||||
--light-color: hsl(0, 0%, 80%);
|
||||
--lighter-color: hsl(0, 0%, 100%);
|
||||
--link-color: hsl(0, 0%, 80%);
|
||||
|
||||
--blue-color: rgb(63, 156, 234);
|
||||
--orange-color: rgb(240, 160, 65);
|
||||
--green-color: rgb(92, 190, 64);
|
||||
--yellow-color: rgb(255, 238, 0);
|
||||
--pink-color: rgb(246, 85, 160);
|
||||
--magenta-color: rgb(214, 39, 246);
|
||||
--red-color: rgb(246, 27, 20);
|
||||
--gray-color: rgb(153, 154, 160);
|
||||
|
||||
--inactive-link-color: var(--light-color);
|
||||
|
||||
--schedule-row-color: hsl(210, 55%, 90%);
|
||||
|
||||
|
||||
|
||||
//MARGINS
|
||||
--small-margin: $small-margin;
|
||||
--half-margin: $half-margin;
|
||||
--single-margin: $single-margin;
|
||||
--double-margin: $double-margin;
|
||||
--post-margin: $post-margin;
|
||||
--small-margin: .2em;
|
||||
--half-margin: .5em;
|
||||
--single-margin: 1.0em;
|
||||
--double-margin: 2.0em;
|
||||
--post-margin: 5.0em;
|
||||
|
||||
//OTHER
|
||||
--block-border-radius: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-width) {
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
:root {
|
||||
//FONTS
|
||||
--smaller-font-size: 0.9rem;
|
||||
--normal-font-size: 1.0rem;
|
||||
--bigger-font-size: 1.1rem;
|
||||
--big-font-size: 1.3rem;
|
||||
--biggest-font-size: 2.0rem;
|
||||
|
||||
--font-size-h1: 1.75rem;
|
||||
--font-size-h2: 1.50rem;
|
||||
--font-size-h3: 1.25rem;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<base href="/">
|
||||
|
||||
<title>Edit Hudozka</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width, maximum-scale=1.0"/>
|
||||
<meta name="referrer" content="unsafe-url">
|
||||
|
||||
<link rel="icon" type="image/png" href="//static.shlisselburg.org/art/graphics/favicon-32.png"/>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="//static.shlisselburg.org/art/graphics/favicon-57.png"/>
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="//static.shlisselburg.org/art/graphics/favicon-72.png"/>
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="//static.shlisselburg.org/art/graphics/favicon-114.png"/>
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="//static.shlisselburg.org/art/graphics/favicon-144.png"/>
|
||||
|
||||
<!--<link href="https://fonts.googleapis.com/css?family=PT+Sans|PT+Serif" rel="stylesheet" as="font">-->
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="edit.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user