feat(pipes): add number (decimal, percent, currency) pipes
This commit is contained in:

committed by
Tobias Bosch

parent
b54e7214f0
commit
3143d188ae
@ -12,4 +12,5 @@ export {ObservablePipe} from './src/change_detection/pipes/observable_pipe';
|
|||||||
export {JsonPipe} from './src/change_detection/pipes/json_pipe';
|
export {JsonPipe} from './src/change_detection/pipes/json_pipe';
|
||||||
export {IterableChanges} from './src/change_detection/pipes/iterable_changes';
|
export {IterableChanges} from './src/change_detection/pipes/iterable_changes';
|
||||||
export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes';
|
export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes';
|
||||||
|
export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe';
|
||||||
export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';
|
export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';
|
||||||
|
@ -14,6 +14,7 @@ dependencies:
|
|||||||
code_transformers: '^0.2.8'
|
code_transformers: '^0.2.8'
|
||||||
dart_style: '^0.1.8'
|
dart_style: '^0.1.8'
|
||||||
html: '^0.12.0'
|
html: '^0.12.0'
|
||||||
|
intl: '^0.12.4'
|
||||||
logging: '>=0.9.0 <0.12.0'
|
logging: '>=0.9.0 <0.12.0'
|
||||||
source_span: '^1.0.0'
|
source_span: '^1.0.0'
|
||||||
stack_trace: '^1.1.1'
|
stack_trace: '^1.1.1'
|
||||||
|
@ -11,6 +11,7 @@ import {UpperCaseFactory} from './pipes/uppercase_pipe';
|
|||||||
import {LowerCaseFactory} from './pipes/lowercase_pipe';
|
import {LowerCaseFactory} from './pipes/lowercase_pipe';
|
||||||
import {JsonPipe} from './pipes/json_pipe';
|
import {JsonPipe} from './pipes/json_pipe';
|
||||||
import {LimitToPipeFactory} from './pipes/limit_to_pipe';
|
import {LimitToPipeFactory} from './pipes/limit_to_pipe';
|
||||||
|
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
|
||||||
import {NullPipeFactory} from './pipes/null_pipe';
|
import {NullPipeFactory} from './pipes/null_pipe';
|
||||||
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
|
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
|
||||||
import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di';
|
import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di';
|
||||||
@ -76,6 +77,30 @@ export const json: List<PipeFactory> =
|
|||||||
export const limitTo: List<PipeFactory> =
|
export const limitTo: List<PipeFactory> =
|
||||||
CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]);
|
CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number number transform.
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
export const decimal: List<PipeFactory> =
|
||||||
|
CONST_EXPR([CONST_EXPR(new DecimalPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percent number transform.
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
export const percent: List<PipeFactory> =
|
||||||
|
CONST_EXPR([CONST_EXPR(new PercentPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currency number transform.
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
export const currency: List<PipeFactory> =
|
||||||
|
CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||||
|
|
||||||
export const defaultPipes = CONST_EXPR({
|
export const defaultPipes = CONST_EXPR({
|
||||||
"iterableDiff": iterableDiff,
|
"iterableDiff": iterableDiff,
|
||||||
"keyValDiff": keyValDiff,
|
"keyValDiff": keyValDiff,
|
||||||
@ -83,7 +108,10 @@ export const defaultPipes = CONST_EXPR({
|
|||||||
"uppercase": uppercase,
|
"uppercase": uppercase,
|
||||||
"lowercase": lowercase,
|
"lowercase": lowercase,
|
||||||
"json": json,
|
"json": json,
|
||||||
"limitTo": limitTo
|
"limitTo": limitTo,
|
||||||
|
"number": decimal,
|
||||||
|
"percent": percent,
|
||||||
|
"currency": currency
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
132
modules/angular2/src/change_detection/pipes/number_pipe.ts
Normal file
132
modules/angular2/src/change_detection/pipes/number_pipe.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
isNumber,
|
||||||
|
isPresent,
|
||||||
|
isBlank,
|
||||||
|
StringWrapper,
|
||||||
|
NumberWrapper,
|
||||||
|
RegExpWrapper,
|
||||||
|
BaseException,
|
||||||
|
CONST,
|
||||||
|
FunctionWrapper
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
|
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {Pipe, BasePipe, PipeFactory} from './pipe';
|
||||||
|
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||||
|
|
||||||
|
var defaultLocale: string = 'en-US';
|
||||||
|
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
|
||||||
|
|
||||||
|
@CONST()
|
||||||
|
export class NumberPipe extends BasePipe implements PipeFactory {
|
||||||
|
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
|
||||||
|
currencyAsSymbol: boolean = false): string {
|
||||||
|
var minInt = 1, minFraction = 0, maxFraction = 3;
|
||||||
|
if (isPresent(digits)) {
|
||||||
|
var parts = RegExpWrapper.firstMatch(_re, digits);
|
||||||
|
if (isBlank(parts)) {
|
||||||
|
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
|
||||||
|
}
|
||||||
|
if (isPresent(parts[1])) { // min integer digits
|
||||||
|
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||||
|
}
|
||||||
|
if (isPresent(parts[3])) { // min fraction digits
|
||||||
|
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||||
|
}
|
||||||
|
if (isPresent(parts[5])) { // max fraction digits
|
||||||
|
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NumberFormatter.format(value, defaultLocale, style, {
|
||||||
|
minimumIntegerDigits: minInt,
|
||||||
|
minimumFractionDigits: minFraction,
|
||||||
|
maximumFractionDigits: maxFraction,
|
||||||
|
currency: currency,
|
||||||
|
currencyAsSymbol: currencyAsSymbol
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
supports(obj): boolean { return isNumber(obj); }
|
||||||
|
|
||||||
|
create(cdRef: ChangeDetectorRef): Pipe { return this }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a number as local text. i.e. group sizing and seperator and other locale-specific
|
||||||
|
* configurations are based on the active locale.
|
||||||
|
*
|
||||||
|
* # Usage
|
||||||
|
*
|
||||||
|
* expression | number[:digitInfo]
|
||||||
|
*
|
||||||
|
* where `expression` is a number and `digitInfo` has the following format:
|
||||||
|
*
|
||||||
|
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
|
||||||
|
*
|
||||||
|
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
|
||||||
|
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
|
||||||
|
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
|
||||||
|
*
|
||||||
|
* For more information on the acceptable range for each of these numbers and other
|
||||||
|
* details see your native internationalization library.
|
||||||
|
*
|
||||||
|
* # Examples
|
||||||
|
*
|
||||||
|
* {{ 123 | number }} // output is 123
|
||||||
|
* {{ 123.1 | number: '.2-3' }} // output is 123.10
|
||||||
|
* {{ 1 | number: '2.2' }} // output is 01.00
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
@CONST()
|
||||||
|
export class DecimalPipe extends NumberPipe {
|
||||||
|
transform(value, args: any[]): string {
|
||||||
|
var digits: string = ListWrapper.first(args);
|
||||||
|
return NumberPipe._format(value, NumberFormatStyle.DECIMAL, digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a number as local percent.
|
||||||
|
*
|
||||||
|
* # Usage
|
||||||
|
*
|
||||||
|
* expression | percent[:digitInfo]
|
||||||
|
*
|
||||||
|
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
@CONST()
|
||||||
|
export class PercentPipe extends NumberPipe {
|
||||||
|
transform(value, args: any[]): string {
|
||||||
|
var digits: string = ListWrapper.first(args);
|
||||||
|
return NumberPipe._format(value, NumberFormatStyle.PERCENT, digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a number as local currency.
|
||||||
|
*
|
||||||
|
* # Usage
|
||||||
|
*
|
||||||
|
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
|
||||||
|
*
|
||||||
|
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
|
||||||
|
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
|
||||||
|
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
|
||||||
|
* is `false`.
|
||||||
|
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||||
|
*
|
||||||
|
* @exportedAs angular2/pipes
|
||||||
|
*/
|
||||||
|
@CONST()
|
||||||
|
export class CurrencyPipe extends NumberPipe {
|
||||||
|
transform(value, args: any[]): string {
|
||||||
|
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
|
||||||
|
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
|
||||||
|
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
|
||||||
|
return NumberPipe._format(value, NumberFormatStyle.CURRENCY, digits, currencyCode,
|
||||||
|
symbolDisplay);
|
||||||
|
}
|
||||||
|
}
|
42
modules/angular2/src/facade/intl.dart
Normal file
42
modules/angular2/src/facade/intl.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
library facade.intl;
|
||||||
|
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
|
||||||
|
|
||||||
|
enum NumberFormatStyle {
|
||||||
|
DECIMAL,
|
||||||
|
PERCENT,
|
||||||
|
CURRENCY
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberFormatter {
|
||||||
|
static String format(num number, String locale, NumberFormatStyle style,
|
||||||
|
{int minimumIntegerDigits: 1,
|
||||||
|
int minimumFractionDigits: 0,
|
||||||
|
int maximumFractionDigits: 3,
|
||||||
|
String currency,
|
||||||
|
bool currencyAsSymbol: false}) {
|
||||||
|
locale = _normalizeLocale(locale);
|
||||||
|
NumberFormat formatter;
|
||||||
|
switch (style) {
|
||||||
|
case NumberFormatStyle.DECIMAL:
|
||||||
|
formatter = new NumberFormat.decimalPattern(locale);
|
||||||
|
break;
|
||||||
|
case NumberFormatStyle.PERCENT:
|
||||||
|
formatter = new NumberFormat.percentPattern(locale);
|
||||||
|
break;
|
||||||
|
case NumberFormatStyle.CURRENCY:
|
||||||
|
if (currencyAsSymbol) {
|
||||||
|
// See https://github.com/dart-lang/intl/issues/59.
|
||||||
|
throw new Exception('Displaying currency as symbol is not supported.');
|
||||||
|
}
|
||||||
|
formatter = new NumberFormat.currencyPattern(locale, currency);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
formatter.minimumIntegerDigits = minimumIntegerDigits;
|
||||||
|
formatter.minimumFractionDigits = minimumFractionDigits;
|
||||||
|
formatter.maximumFractionDigits = maximumFractionDigits;
|
||||||
|
return formatter.format(number);
|
||||||
|
}
|
||||||
|
}
|
73
modules/angular2/src/facade/intl.ts
Normal file
73
modules/angular2/src/facade/intl.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
// Modified version of internal Typescript intl.d.ts.
|
||||||
|
// TODO(piloopin): remove when https://github.com/Microsoft/TypeScript/issues/3521 is shipped.
|
||||||
|
declare module Intl {
|
||||||
|
interface NumberFormatOptions {
|
||||||
|
localeMatcher?: string;
|
||||||
|
style?: string;
|
||||||
|
currency?: string;
|
||||||
|
currencyDisplay?: string;
|
||||||
|
useGrouping?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NumberFormat {
|
||||||
|
format(value: number): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
var NumberFormat: {
|
||||||
|
new (locale?: string, options?: NumberFormatOptions): NumberFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DateTimeFormatOptions {
|
||||||
|
localeMatcher?: string;
|
||||||
|
weekday?: string;
|
||||||
|
era?: string;
|
||||||
|
year?: string;
|
||||||
|
month?: string;
|
||||||
|
day?: string;
|
||||||
|
hour?: string;
|
||||||
|
minute?: string;
|
||||||
|
second?: string;
|
||||||
|
timeZoneName?: string;
|
||||||
|
formatMatcher?: string;
|
||||||
|
hour12?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DateTimeFormat {
|
||||||
|
format(date?: Date | number): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
var DateTimeFormat: {
|
||||||
|
new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NumberFormatStyle {
|
||||||
|
DECIMAL,
|
||||||
|
PERCENT,
|
||||||
|
CURRENCY
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NumberFormatter {
|
||||||
|
static format(number: number, locale: string, style: NumberFormatStyle,
|
||||||
|
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3,
|
||||||
|
currency, currencyAsSymbol = false}: {
|
||||||
|
minimumIntegerDigits?: int,
|
||||||
|
minimumFractionDigits?: int,
|
||||||
|
maximumFractionDigits?: int,
|
||||||
|
currency?: string,
|
||||||
|
currencyAsSymbol?: boolean
|
||||||
|
} = {}): string {
|
||||||
|
var intlOptions: Intl.NumberFormatOptions = {
|
||||||
|
minimumIntegerDigits: minimumIntegerDigits,
|
||||||
|
minimumFractionDigits: minimumFractionDigits,
|
||||||
|
maximumFractionDigits: maximumFractionDigits
|
||||||
|
};
|
||||||
|
intlOptions.style = NumberFormatStyle[style].toLowerCase();
|
||||||
|
if (style == NumberFormatStyle.CURRENCY) {
|
||||||
|
intlOptions.currency = currency;
|
||||||
|
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat(locale, intlOptions).format(number);
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ bool isType(obj) => obj is Type;
|
|||||||
bool isStringMap(obj) => obj is Map;
|
bool isStringMap(obj) => obj is Map;
|
||||||
bool isArray(obj) => obj is List;
|
bool isArray(obj) => obj is List;
|
||||||
bool isPromise(obj) => obj is Future;
|
bool isPromise(obj) => obj is Future;
|
||||||
|
bool isNumber(obj) => obj is num;
|
||||||
|
|
||||||
String stringify(obj) => obj.toString();
|
String stringify(obj) => obj.toString();
|
||||||
|
|
||||||
|
@ -89,6 +89,10 @@ export function isArray(obj): boolean {
|
|||||||
return Array.isArray(obj);
|
return Array.isArray(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNumber(obj): boolean {
|
||||||
|
return typeof obj === 'number';
|
||||||
|
}
|
||||||
|
|
||||||
export function stringify(token): string {
|
export function stringify(token): string {
|
||||||
if (typeof token === 'string') {
|
if (typeof token === 'string') {
|
||||||
return token;
|
return token;
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DecimalPipe,
|
||||||
|
PercentPipe,
|
||||||
|
CurrencyPipe
|
||||||
|
} from 'angular2/src/change_detection/pipes/number_pipe';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe("DecimalPipe", () => {
|
||||||
|
var pipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new DecimalPipe(); });
|
||||||
|
|
||||||
|
describe("supports", () => {
|
||||||
|
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||||
|
|
||||||
|
it("should not support other objects", () => {
|
||||||
|
expect(pipe.supports(new Object())).toBe(false);
|
||||||
|
expect(pipe.supports('str')).toBe(false);
|
||||||
|
expect(pipe.supports(null)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("transform", () => {
|
||||||
|
it('should return correct value', () => {
|
||||||
|
expect(pipe.transform(12345, [])).toEqual('12,345');
|
||||||
|
expect(pipe.transform(123, ['.2'])).toEqual('123.00');
|
||||||
|
expect(pipe.transform(1, ['3.'])).toEqual('001');
|
||||||
|
expect(pipe.transform(1.1, ['3.4-5'])).toEqual('001.1000');
|
||||||
|
expect(pipe.transform(1.123456, ['3.4-5'])).toEqual('001.12346');
|
||||||
|
expect(pipe.transform(1.1234, [])).toEqual('1.123');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("PercentPipe", () => {
|
||||||
|
var pipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new PercentPipe(); });
|
||||||
|
|
||||||
|
describe("supports", () => {
|
||||||
|
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||||
|
|
||||||
|
it("should not support other objects", () => {
|
||||||
|
expect(pipe.supports(new Object())).toBe(false);
|
||||||
|
expect(pipe.supports('str')).toBe(false);
|
||||||
|
expect(pipe.supports(null)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("transform", () => {
|
||||||
|
it('should return correct value', () => {
|
||||||
|
expect(pipe.transform(1.23, [])).toEqual('123%');
|
||||||
|
expect(pipe.transform(1.2, ['.2'])).toEqual('120.00%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CurrencyPipe", () => {
|
||||||
|
var pipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new CurrencyPipe(); });
|
||||||
|
|
||||||
|
describe("supports", () => {
|
||||||
|
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||||
|
|
||||||
|
it("should not support other objects", () => {
|
||||||
|
expect(pipe.supports(new Object())).toBe(false);
|
||||||
|
expect(pipe.supports('str')).toBe(false);
|
||||||
|
expect(pipe.supports(null)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("transform", () => {
|
||||||
|
it('should return correct value', () => {
|
||||||
|
expect(pipe.transform(123, [])).toEqual('USD123');
|
||||||
|
expect(pipe.transform(12, ['EUR', false, '.2'])).toEqual('EUR12.00');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -7,6 +7,8 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angular2:
|
angular2:
|
||||||
path: ../angular2
|
path: ../angular2
|
||||||
|
dependency_overrides:
|
||||||
|
intl: '^0.12.4' # angular depends on an older version of intl.
|
||||||
transformers:
|
transformers:
|
||||||
- angular:
|
- angular:
|
||||||
$exclude: "web/e2e_test"
|
$exclude: "web/e2e_test"
|
||||||
|
@ -3,5 +3,6 @@ environment:
|
|||||||
sdk: '>=1.9.0 <2.0.0'
|
sdk: '>=1.9.0 <2.0.0'
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
guinness: '^0.1.17'
|
guinness: '^0.1.17'
|
||||||
|
intl: '^0.12.4'
|
||||||
unittest: '^0.11.5+4'
|
unittest: '^0.11.5+4'
|
||||||
quiver: '^0.21.4'
|
quiver: '^0.21.4'
|
||||||
|
Reference in New Issue
Block a user