diff --git a/backend/routes/route/post.js b/backend/routes/route/post.js index 915989a..b6b1ce2 100644 --- a/backend/routes/route/post.js +++ b/backend/routes/route/post.js @@ -1,6 +1,6 @@ const { User, Route } = require('../../models'); -const { parseRoute, parseStickers, parseString, parseNumber } = require('../../utils/parse'); +const { parseRoute, parseStickers, parseString, parseNumber, parseAddress } = require('../../utils/parse'); module.exports = async (req, res) => { const { body, body: { id, token, force } } = req; @@ -9,7 +9,7 @@ module.exports = async (req, res) => { if (!owner) return res.send({ success: false, reason: 'unauthorized', id, token }); const title = parseString(body.title, 64); - const address = parseString(body.address, 32); + const address = parseAddress(body.address, 32) const route = parseRoute(body.route); const stickers = parseStickers(body.stickers); const logo = parseString(body.logo, 16); diff --git a/backend/utils/parse.js b/backend/utils/parse.js index 32e884b..4e023ff 100644 --- a/backend/utils/parse.js +++ b/backend/utils/parse.js @@ -21,5 +21,10 @@ module.exports.parseStickers = stickers => stickers.filter(el => ( )); // .map(el => ((el.text && String(el.text).substr(0, 100)) || '')); -module.exports.parseString = (value, size) => (value && String(value).substr(0, size)) || ''; +const parseString = (value, size) => (value && String(value).substr(0, size)) || ''; module.exports.parseNumber = (value, min, max) => (value && Number(value) && Math.min(max, Math.max(min, value))) || 0; + +module.exports.parseString = parseString; +module.exports.parseAddress = (value, size) => ( + parseString(value, size).replace(/[^A-Za-z\-_0-9]/ig, '_').replace(/_{2,}/ig, '_') +); diff --git a/src/components/dialogs/TitleDialog.tsx b/src/components/dialogs/TitleDialog.tsx index b8fe6c2..91291c0 100644 --- a/src/components/dialogs/TitleDialog.tsx +++ b/src/components/dialogs/TitleDialog.tsx @@ -2,19 +2,47 @@ import * as React from 'react'; import { bindActionCreators } from "redux"; import { connect } from 'react-redux'; +import classnames from 'classnames'; + interface ITitleDialogProps { editing: boolean, title?: string, } -export const Component: React.FunctionComponent = ({ editing, title }) => ( - !editing && title && -
-
-

{title}

+interface ITitleDialogState { + raised: boolean; +} + +export class Component extends React.PureComponent { + state = { + raised: false, + }; + + onHover = () => this.setState({ raised: true }); + onLeave = () => this.setState({ raised: false }); + + render() { + const { editing, title } = this.props; + + return ( +
+
{ this.sizer = el; }}> +
+
+

{title}

+
+
+ Давно выяснено, что при оценке дизайна и композиции читаемый текст мешает сосредоточиться. Lorem Ipsum используют потому, что тот обеспечивает более или менее стандартное заполнение шаблона, а также реальное распределение букв и пробелов в абзацах, которое не получается при простой дубликации "Здесь ваш текст.. Здесь ваш текст.. Здесь ваш текст.." Многие программы электронной вёрстки и редакторы HTML используют Lorem Ipsum в качестве текста по умолчанию, так что поиск по +
+
+
-
-); + ) + } + + text; + sizer; +} const mapStateToProps = ({ user: { editing, title } }) => ({ editing, title }); const mapDispatchToProps = dispatch => bindActionCreators({ }, dispatch); diff --git a/src/styles/panel.less b/src/styles/panel.less index b35dfe8..ade2a81 100644 --- a/src/styles/panel.less +++ b/src/styles/panel.less @@ -616,20 +616,48 @@ } } -.title-dialog { +.title-dialog-wrapper { position: fixed; - left: 10px; - bottom: 68px; - width: 340px; + bottom: 0; + left: 0; + width: 360px; + height: 100%; + background: transparent; z-index: 2; - opacity: 0.5; - transition: opacity 500ms; + padding: 10px 10px 58px 10px; + box-sizing: border-box; + display: flex; + align-items: flex-end; + pointer-events: none; + touch-action: none; +} - &:hover { - opacity: 1; +.title-dialog-sizer { + height: 100%; + width: 100%; + display: flex; + align-items: flex-end; +} + +.title-dialog { + z-index: 2; + opacity: 0; + transition: opacity 500ms, transform 1s; + transform: translate(0, 68px); + user-select: none; + pointer-events: all; + touch-action: auto; + + &.active { + opacity: 0.5; + transform: translate(0, 0); + &:hover { + opacity: 1; + } } .title-dialog-pane { + margin-bottom: 10px; padding: 10px; background: #111111; color: fade(white, 50%); diff --git a/src/utils/format.ts b/src/utils/format.ts index 7777f17..f0c55a5 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,6 +1,8 @@ const ru = [' ','\\.',',',':','\\?','#','Я','я','Ю','ю','Ч','ч','Ш','ш','Щ','щ','Ж','ж','А','а','Б','б','В','в','Г','г','Д','д','Е','е','Ё','ё','З','з','И','и','Й','й','К','к','Л','л','М','м','Н','н', 'О','о','П','п','Р','р','С','с','Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ы','ы','Ь','ь','Ъ','ъ','Э','э']; const en = ['_','','','','','','Ya','ya','Yu','yu','Ch','ch','Sh','sh','Sh','sh','Zh','zh','A','a','B','b','V','v','G','g','D','d','E','e','E','e','Z','z','I','i','J','j','K','k','L','l','M','m','N','n', 'O','o','P','p','R','r','S','s','T','t','U','u','F','f','H','h','C','c','Y','y','','','','','E', 'e']; +export const removeGarbage = (text: string): string => text.replace(/[^A-Za-z\-_0-9]/ig, '_').replace(/_{2,}/ig, '_'); + export const toHours = (info: number): string => { const hrs = parseInt(String(info), 10); const min = Math.round((Number(info) - hrs) * 60); @@ -9,5 +11,5 @@ export const toHours = (info: number): string => { }; export const toTranslit = (string: string): string => ( - ru.reduce((text, el, i) => (text.replace(new RegExp(ru[i], 'g'), en[i])), (String(string) || '')) + removeGarbage(ru.reduce((text, el, i) => (text.replace(new RegExp(ru[i], 'g'), en[i])), (String(string) || ''))) );