mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-30 17:00:35 +02:00
[WIP] add string length indicator and max-length message
This commit is contained in:
@@ -2324,6 +2324,36 @@ div.combobox {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-field-input-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-field-input-wrap span.length-indicator-wrap {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.form-field-input-wrap > textarea:focus + span.length-indicator-wrap,
|
||||
.form-field-input-wrap > textarea:focus + div.combobox-caret + span.length-indicator-wrap {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.form-field-input-wrap span.length-indicator {
|
||||
display: block;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background-color: #7092ff;
|
||||
border-right-style: solid;
|
||||
border-right-color: lightgray;
|
||||
}
|
||||
|
||||
.form-field-input-wrap span.length-indicator.limit-reached {
|
||||
border-right-color: red;
|
||||
}
|
||||
|
||||
/* Field Help
|
||||
------------------------------------------------------- */
|
||||
|
||||
@@ -782,6 +782,7 @@ en:
|
||||
foot: ft
|
||||
# abbreviation of inches
|
||||
inch: in
|
||||
max_length_reached: "Please keep in mind that texts cannot be longer than a maximum of {maxChars} characters. Anything exceeding that length will be truncated."
|
||||
background:
|
||||
title: Background
|
||||
description: Background Settings
|
||||
|
||||
@@ -19,7 +19,7 @@ import { presetManager } from '../presets';
|
||||
import { rendererBackground, rendererFeatures, rendererMap, rendererPhotos } from '../renderer';
|
||||
import { services } from '../services';
|
||||
import { uiInit } from '../ui/init';
|
||||
import { utilKeybinding, utilRebind, utilStringQs, utilUnicodeCharsTruncated } from '../util';
|
||||
import { utilKeybinding, utilRebind, utilStringQs, utilCleanOsmString } from '../util';
|
||||
|
||||
|
||||
export function coreContext() {
|
||||
@@ -220,26 +220,9 @@ export function coreContext() {
|
||||
context.maxCharsForTagValue = () => 255;
|
||||
context.maxCharsForRelationRole = () => 255;
|
||||
|
||||
function cleanOsmString(val, maxChars) {
|
||||
// be lenient with input
|
||||
if (val === undefined || val === null) {
|
||||
val = '';
|
||||
} else {
|
||||
val = val.toString();
|
||||
}
|
||||
|
||||
// remove whitespace
|
||||
val = val.trim();
|
||||
|
||||
// use the canonical form of the string
|
||||
if (val.normalize) val = val.normalize('NFC');
|
||||
|
||||
// trim to the number of allowed characters
|
||||
return utilUnicodeCharsTruncated(val, maxChars);
|
||||
}
|
||||
context.cleanTagKey = (val) => cleanOsmString(val, context.maxCharsForTagKey());
|
||||
context.cleanTagValue = (val) => cleanOsmString(val, context.maxCharsForTagValue());
|
||||
context.cleanRelationRole = (val) => cleanOsmString(val, context.maxCharsForRelationRole());
|
||||
context.cleanTagKey = (val) => utilCleanOsmString(val, context.maxCharsForTagKey());
|
||||
context.cleanTagValue = (val) => utilCleanOsmString(val, context.maxCharsForTagValue());
|
||||
context.cleanRelationRole = (val) => utilCleanOsmString(val, context.maxCharsForRelationRole());
|
||||
|
||||
|
||||
/* History */
|
||||
|
||||
@@ -156,7 +156,7 @@ osmEntity.prototype = {
|
||||
changed = true;
|
||||
merged[k] = utilUnicodeCharsTruncated(
|
||||
utilArrayUnion(t1.split(/;\s*/), t2.split(/;\s*/)).join(';'),
|
||||
255 // avoid exceeding character limit; see also services/osm.js -> maxCharsForTagValue()
|
||||
255 // avoid exceeding character limit; see also context.maxCharsForTagValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
utilNoAuto,
|
||||
utilRebind
|
||||
} from '../../util';
|
||||
import { uiLengthIndicator } from '../length_indicator';
|
||||
|
||||
|
||||
export function uiFieldTextarea(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var input = d3_select(null);
|
||||
var _lengthIndicator = uiLengthIndicator(context.maxCharsForTagValue());
|
||||
var _tags;
|
||||
|
||||
|
||||
@@ -22,6 +24,7 @@ export function uiFieldTextarea(field, context) {
|
||||
wrap = wrap.enter()
|
||||
.append('div')
|
||||
.attr('class', 'form-field-input-wrap form-field-input-' + field.type)
|
||||
.style('position', 'relative')
|
||||
.merge(wrap);
|
||||
|
||||
input = wrap.selectAll('textarea')
|
||||
@@ -35,22 +38,23 @@ export function uiFieldTextarea(field, context) {
|
||||
.on('blur', change())
|
||||
.on('change', change())
|
||||
.merge(input);
|
||||
}
|
||||
|
||||
wrap.call(_lengthIndicator);
|
||||
|
||||
function change(onInput) {
|
||||
return function() {
|
||||
function change(onInput) {
|
||||
return function() {
|
||||
|
||||
var val = utilGetSetValue(input);
|
||||
if (!onInput) val = context.cleanTagValue(val);
|
||||
var val = utilGetSetValue(input);
|
||||
if (!onInput) val = context.cleanTagValue(val);
|
||||
|
||||
// don't override multiple values with blank string
|
||||
if (!val && Array.isArray(_tags[field.key])) return;
|
||||
// don't override multiple values with blank string
|
||||
if (!val && Array.isArray(_tags[field.key])) return;
|
||||
|
||||
var t = {};
|
||||
t[field.key] = val || undefined;
|
||||
dispatch.call('change', this, t, onInput);
|
||||
};
|
||||
var t = {};
|
||||
t[field.key] = val || undefined;
|
||||
dispatch.call('change', this, t, onInput);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +67,10 @@ export function uiFieldTextarea(field, context) {
|
||||
.attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined)
|
||||
.attr('placeholder', isMixed ? t('inspector.multiple_values') : (field.placeholder() || t('inspector.unknown')))
|
||||
.classed('mixed', isMixed);
|
||||
|
||||
if (!isMixed) {
|
||||
_lengthIndicator.update(tags[field.key]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export { uiIssuesInfo } from './issues_info';
|
||||
export { uiKeepRightDetails } from './keepRight_details';
|
||||
export { uiKeepRightEditor } from './keepRight_editor';
|
||||
export { uiKeepRightHeader } from './keepRight_header';
|
||||
export { uiLengthIndicator } from './length_indicator';
|
||||
export { uiLasso } from './lasso';
|
||||
export { uiLoading } from './loading';
|
||||
export { uiMapInMap } from './map_in_map';
|
||||
|
||||
58
modules/ui/length_indicator.js
Normal file
58
modules/ui/length_indicator.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t } from '../core/localizer';
|
||||
import {
|
||||
utilUnicodeCharsCount,
|
||||
utilCleanOsmString
|
||||
} from '../util';
|
||||
import { uiPopover } from './popover';
|
||||
|
||||
|
||||
export function uiLengthIndicator(maxChars) {
|
||||
var _wrap = d3_select(null);
|
||||
var _tooltip = uiPopover('tooltip')
|
||||
.placement('bottom')
|
||||
.hasArrow(true)
|
||||
.content(() => selection => {
|
||||
selection.text('');
|
||||
t.append('inspector.max_length_reached', { maxChars })(selection);
|
||||
});
|
||||
|
||||
var lengthIndicator = function(selection) {
|
||||
_wrap = selection.selectAll('span.length-indicator-wrap').data([0]);
|
||||
_wrap = _wrap.enter()
|
||||
.append('span')
|
||||
.merge(_wrap)
|
||||
.classed('length-indicator-wrap', true);
|
||||
selection.call(_tooltip);
|
||||
};
|
||||
|
||||
lengthIndicator.update = function(val) {
|
||||
const strLen = utilUnicodeCharsCount(utilCleanOsmString(val, Number.POSITIVE_INFINITY));
|
||||
|
||||
var lengthIndicator = _wrap.selectAll('span.length-indicator')
|
||||
.data([strLen]);
|
||||
|
||||
lengthIndicator = lengthIndicator.enter()
|
||||
.append('span')
|
||||
.merge(lengthIndicator)
|
||||
.classed('length-indicator', true)
|
||||
.classed('limit-reached', d => d > maxChars)
|
||||
.style('border-right-width', d => `${Math.abs(maxChars - d) * 2}px`)
|
||||
.style('margin-right', d => d > maxChars
|
||||
? `${(maxChars - d) * 2}px`
|
||||
: 0)
|
||||
.style('opacity', d => d > maxChars * 0.8
|
||||
? Math.min(1, (d / maxChars - 0.8) / (1 - 0.8))
|
||||
: 0)
|
||||
.style('pointer-events', d => d > maxChars * 0.8 ? null: 'none');
|
||||
|
||||
if (strLen > maxChars) {
|
||||
_tooltip.show();
|
||||
} else {
|
||||
_tooltip.hide();
|
||||
}
|
||||
}
|
||||
|
||||
return lengthIndicator;
|
||||
}
|
||||
@@ -54,3 +54,4 @@ export { utilUnicodeCharsCount } from './util';
|
||||
export { utilUnicodeCharsTruncated } from './util';
|
||||
export { utilUniqueDomId } from './util';
|
||||
export { utilWrap } from './util';
|
||||
export { utilCleanOsmString } from './util';
|
||||
|
||||
@@ -629,3 +629,22 @@ export function utilOldestID(ids) {
|
||||
|
||||
return ids[oldestIDIndex];
|
||||
}
|
||||
|
||||
// returns a normalized and truncated string to `maxChars` utf-8 characters
|
||||
export function utilCleanOsmString(val, maxChars) {
|
||||
// be lenient with input
|
||||
if (val === undefined || val === null) {
|
||||
val = '';
|
||||
} else {
|
||||
val = val.toString();
|
||||
}
|
||||
|
||||
// remove whitespace
|
||||
val = val.trim();
|
||||
|
||||
// use the canonical form of the string
|
||||
if (val.normalize) val = val.normalize('NFC');
|
||||
|
||||
// trim to the number of allowed characters
|
||||
return utilUnicodeCharsTruncated(val, maxChars);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user