UNPKG

ngx-scrolltop

Version:

Lightweight, Material Design inspired button for scroll-to-top of the page. No dependencies. Pure Angular!

215 lines (188 loc) 5.92 kB
/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { JsonAstArray, JsonAstKeyValue, JsonAstNode, JsonAstObject, JsonValue, } from '@angular-devkit/core'; import { UpdateRecorder } from '@angular-devkit/schematics'; export function appendPropertyInAstObject( recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, indent: number, ) { const indentStr = _buildIndent(indent); let index = node.start.offset + 1; if (node.properties.length > 0) { // Insert comma. const last = node.properties[node.properties.length - 1]; const { text, end } = last; const commaIndex = text.endsWith('\n') ? end.offset - 1 : end.offset; recorder.insertRight(commaIndex, ','); index = end.offset; } const content = _stringifyContent(value, indentStr); recorder.insertRight( index, (node.properties.length === 0 && indent ? '\n' : '') + ' '.repeat(indent) + `"${propertyName}":${indent ? ' ' : ''}${content}` + indentStr.slice(0, -indent), ); } export function insertPropertyInAstObjectInOrder( recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, indent: number, ) { if (node.properties.length === 0) { appendPropertyInAstObject(recorder, node, propertyName, value, indent); return; } // Find insertion info. let insertAfterProp: JsonAstKeyValue | null = null; let prev: JsonAstKeyValue | null = null; let isLastProp = false; const last = node.properties[node.properties.length - 1]; for (const prop of node.properties) { if (prop.key.value > propertyName) { if (prev) { insertAfterProp = prev; } break; } if (prop === last) { isLastProp = true; insertAfterProp = last; } prev = prop; } if (isLastProp) { appendPropertyInAstObject(recorder, node, propertyName, value, indent); return; } const indentStr = _buildIndent(indent); const insertIndex = insertAfterProp === null ? node.start.offset + 1 : insertAfterProp.end.offset + 1; const content = _stringifyContent(value, indentStr); recorder.insertRight( insertIndex, indentStr + `"${propertyName}":${indent ? ' ' : ''}${content}` + ',', ); } export function removePropertyInAstObject( recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, ) { // Find the property inside the object. const propIdx = node.properties.findIndex(prop => prop.key.value === propertyName); if (propIdx === -1) { // There's nothing to remove. return; } if (node.properties.length === 1) { // This is a special case. Everything should be removed, including indentation. recorder.remove(node.start.offset, node.end.offset - node.start.offset); recorder.insertRight(node.start.offset, '{}'); return; } // The AST considers commas and indentation to be part of the preceding property. // To get around messy comma and identation management, we can work over the range between // two properties instead. const previousProp = node.properties[propIdx - 1]; const targetProp = node.properties[propIdx]; const nextProp = node.properties[propIdx + 1]; let start, end; if (previousProp) { // Given the object below, and intending to remove the `m` property: // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}" // ^---------------^ // Removing the range above results in: // "{\n \"a\": \"a\",\n \"z\": \"z\"\n}" start = previousProp.end; end = targetProp.end; } else { // If there's no previousProp there is a nextProp, since we've specialcased the 1 length case. // Given the object below, and intending to remove the `a` property: // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}" // ^---------------^ // Removing the range above results in: // "{\n \"m\": \"m\",\n \"z\": \"z\"\n}" start = targetProp.start; end = nextProp.start; } recorder.remove(start.offset, end.offset - start.offset); if (!nextProp) { recorder.insertRight(start.offset, '\n'); } } export function appendValueInAstArray( recorder: UpdateRecorder, node: JsonAstArray, value: JsonValue, indent = 4, ) { const indentStr = _buildIndent(indent); let index = node.start.offset + 1; if (node.elements.length > 0) { // Insert comma. const last = node.elements[node.elements.length - 1]; recorder.insertRight(last.end.offset, ','); index = indent ? last.end.offset + 1 : last.end.offset; } recorder.insertRight( index, (node.elements.length === 0 && indent ? '\n' : '') + ' '.repeat(indent) + _stringifyContent(value, indentStr) + indentStr.slice(0, -indent), ); } export function findPropertyInAstObject( node: JsonAstObject, propertyName: string, ): JsonAstNode | null { let maybeNode: JsonAstNode | null = null; for (const property of node.properties) { if (property.key.value == propertyName) { maybeNode = property.value; } } return maybeNode; } function _buildIndent(count: number): string { return count ? '\n' + ' '.repeat(count) : ''; } function _stringifyContent(value: JsonValue, indentStr: string): string { // TODO: Add snapshot tests // The 'space' value is 2, because we want to add 2 additional // indents from the 'key' node. // If we use the indent provided we will have double indents: // "budgets": [ // { // "type": "initial", // "maximumWarning": "2mb", // "maximumError": "5mb" // }, // { // "type": "anyComponentStyle", // 'maximumWarning": "5kb" // } // ] return JSON.stringify(value, null, 2).replace(/\n/g, indentStr); }