editor.clearHistory(); works in CodeMirror 5 I believe, but what about CodeMirror 6?
interface HistoryConfig {
/**
The minimum depth (amount of events) to store. Defaults to 100.
*/
minDepth?: number;
/**
The maximum time (in milliseconds) that adjacent events can be
apart and still be grouped together. Defaults to 500.
*/
newGroupDelay?: number;
}
/**
Create a history extension with the given configuration.
*/
declare function history(config?: HistoryConfig): Extension;
import { EditorView } from '#codemirror/view';
import { EditorState } from '#codemirror/state';
import { history } from '#codemirror/commands';
const state = EditorState.create({
doc: 'my source code',
extensions: [history({ minDepth: 10 })],
});
const view = new EditorView({
parent: document.querySelector('#editor'),
state,
});
Related
In a minimum viable JupyterLab extension, as for example tested using the JupyterLab Plugin Playground, how can I add a toolbar button that will toggle a particular class attribute on the HTML associated with one or more selected notebook cells (either code cell or markdown cell)?
To generalise the example further:
how would I apply different class attributes to code cells and markdown cells?
how would I add a class to the HTML based on the the presence of a particular metadata attribute or metadata tag element in the notebook JSON structure?
As a starting point the following code (taken from the JupyterLab extension examples) should add a button to the toolbar:
import { IDisposable, DisposableDelegate } from '#lumino/disposable';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '#jupyterlab/application';
import { ToolbarButton } from '#jupyterlab/apputils';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import {
NotebookActions,
NotebookPanel,
INotebookModel,
} from '#jupyterlab/notebook';
/**
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'toolbar-button',
autoStart: true,
};
/**
* A notebook widget extension that adds a button to the toolbar.
*/
export class ButtonExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension for the notebook panel widget.
*
* #param panel Notebook panel
* #param context Notebook context
* #returns Disposable on the added button
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const myButtonAction = () => {
// Perform some action
};
const button = new ToolbarButton({
className: 'my-action-button',
label: 'My Button',
onClick: myButtonAction,
tooltip: 'Perform My Button action',
});
panel.toolbar.insertItem(10, 'myNewAction', button);
return new DisposableDelegate(() => {
button.dispose();
});
}
}
/**
* Activate the extension.
*
* #param app Main application object
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
}
/**
* Export the plugin as default.
*/
export default plugin;
You shall start by getting a handle of the Notebook class instance (which is the content of the notebook panel which you already have available):
// in anonymous function assigned to myButtonAction
const notebook = panel.content;
The notebook instance provides you with the active Cell widget:
const activeCell = notebook.activeCell;
The cell widget has two attributes of interest: model which enables you to access the metadata, and node which enables manipulation of the DOM structure.
For example, you could toggle class of the node of a cell if it is a markdown cell (=ICellModel has .type (CellType) equal to 'markdown'):
if (activeCell.model.type === 'markdown') {
activeCell.node.classList.toggle('someClass');
}
The metadata is stored in cell.model.metadata.
For selection of cells something as follows should work:
const {head, anchor} = notebook.getContiguousSelection();
if (head === null || anchor === null) {
// no selection
return;
}
const start = head > anchor ? anchor : head;
const end = head > anchor ? head : anchor;
for (let cellIndex = start; cellIndex <= end; cellIndex++) {
const cell = notebook.widgets[cellIndex];
if (cell.model.type === 'code') {
cell.node.classList.toggle('someOtherClass');
}
}
There is a problem with this approach however, as when the notebook gets opened in a separate view, or simply reloaded, the classes will go away (as they are only toggled on the DOM nodes). If you require persistence, I would recommend to:
use the button to only write to cell metadata
add a separate callback function which will listen to any changes to the notebook model, roughly (not tested!):
// in createNew()
const notebook = panel.content;
notebook.modelChanged.connect((notebook) => {
// iterate cells and toggle DOM classes as needed, e.g.
for (const cell of notebook.widgets) {
if (cell.model.metadata.get('someMetaData')) {
cell.node.classList.toggle('someOtherClass');
}
}
});
which should also work (in principle) with collaborative editing.
Sometimes when I update the snapshots I got an Attribute ngContext and for fix this problem I've to clean and install my node_modules to "fix" this issue.
I've to do this every time that I need to update a snapshot. I've already searched on multiple solutions and nothing worked.
snapshotSerializers: \[
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
\],
Can someone help me with this, please?
Here is an image
I've updated the jest versions and also the jest-present-angular too but didn't work.
I just want to have a solution that does not makes me clean install the node_modules every time
This is indeed annoying especially because it tends to change after upgrading angular version. My snapshots are now failing as well because of this difference :-/.
- __ngContext__={[Function LRootView]}
+ __ngContext__="0"
So, having look at the jest configuration, the snapshot serializers are being loaded from 'jest-preset-angular' module.
The relevant plugin here is 'jest-preset-angular/build/serializers/ng-snapshot'. Now, they are two ways what to do to get rid of __ngContext__.
replace the plugin entirely by a modified copy
Create a copy of that file in the same directory and adapt it accordingly (line https://github.com/thymikee/jest-preset-angular/blob/40b769b8eba0b82913827793b6d9fe06d41808d9/src/serializers/ng-snapshot.ts#L69):
const attributes = Object.keys(componentInstance).filter(key => key !== '__ngContext__');
Adapt the configuration:
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'./custom-snapshot-serializer.ts',
'jest-preset-angular/build/serializers/html-comment',
],
The disadvantage of this solution is that you have to maintain the plugin although only one line has been changed.
replace the plugin by a wrapper (preferred solution)
This creates just a wrapper for the original implementation. The idea is to remove __ngContext__ before it moves on down the plugin chain. However, the logic of the original plugin is used for the fixture serialization.
import type { ComponentRef, DebugNode, Type, ɵCssSelectorList } from '#angular/core';
import type { ComponentFixture } from '#angular/core/testing';
import type { Colors } from 'pretty-format';
import { test as origTest, print as origPrint } from 'jest-preset-angular/build/serializers/ng-snapshot';
/**
* The follow interfaces are customized heavily inspired by #angular/core/core.d.ts
*/
interface ComponentDef {
selectors: ɵCssSelectorList;
}
interface IvyComponentType extends Type<unknown> {
ɵcmp: ComponentDef;
}
interface NgComponentRef extends ComponentRef<unknown> {
componentType: IvyComponentType;
_elDef: any; // eslint-disable-line #typescript-eslint/no-explicit-any
_view: any; // eslint-disable-line #typescript-eslint/no-explicit-any
}
interface NgComponentFixture extends ComponentFixture<unknown> {
componentRef: NgComponentRef;
// eslint-disable-next-line #typescript-eslint/no-explicit-any
componentInstance: Record<string, any>;
}
/**
* The following types haven't been exported by jest so temporarily we copy typings from 'pretty-format'
*/
interface PluginOptions {
edgeSpacing: string;
min: boolean;
spacing: string;
}
type Indent = (indentSpaces: string) => string;
type Printer = (elementToSerialize: unknown) => string;
export const print = (fixture: any, print: Printer, indent: Indent, opts: PluginOptions, colors: Colors): any => {
const componentInstance = (fixture as NgComponentFixture).componentInstance;
const instance = { ...componentInstance };
delete instance.__ngContext__;
const modifiedFixture = { ...fixture, componentInstance: { ...instance } };
return origPrint(modifiedFixture, print, indent, opts, colors);
};
// eslint-disable-next-line #typescript-eslint/no-explicit-any, #typescript-eslint/explicit-module-boundary-types
export const test = (val: any): boolean => {
return origTest(val);
};
The configuration is adapted the same way as before.
I am trying to implement the ability to switch between imperial and metric units using the useContext() hook. So far, I have not had much success. I have read the React useContext() documentation and followed this article. This is my current JavaScript source code that currently does not function as intended:
App.js - where context provider is located
import React from 'react';
import WeatherScreen from './components/screens/WeatherScreen';
import { CurrentUnitContext } from './components/hooks/CurrentUnitContext.js';
export default App = () => {
return (
<CurrentUnitContext.Provider
value={"imperial"}
>
<WeatherScreen />
</CurrentUnitContext.Provider>
);
};
CurrentUnitContext.js - Intended for createContext()
import { createContext } from 'react';
/**
* WIP custom hook for setting current unit
*
* todo - change weather units here
* set imperial (F), metric (C), and maybe standard (K)
*/
export default CurrentUnitContext = createContext();
/**
export default CurrentUnitContext = createContext({
theCurrentUnit: "imperial",
setTheCurrentUnit: () => {},
});
*/
UnitSwitch.js - Intended to contain the component to set the units with user feedback.
import React, { useState, useEffect, useContext } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { CurrentUnitContext } from '../../hooks/CurrentUnitContext.js';
import { tailwind, fontConfig } from '../../../tailwind.js';
/**
* Unit Switcher child component. Based on switch's boolean
* value useState hook isEnabled, set useEffect hook currentUnit
* to either "imperial" or "metric".
*
* #param - switchHeader: Displays the unit switch header
* text passed from WeatherContent.js
* #param - imperialUnits: Text passed from WeatherContext.js for
* imperial unit button string.
* #param - metricUnits: Text passed from WeatherContext.js for
* metric unit button string.
*/
export default UnitSwitch = ({ imperialUnits, metricUnits, switchHeader }) => {
const [theCurrentUnitSetting, setTheCurrentUnitSetting] = useState("imperial");
return (
<View style={tailwind('bg-gray-300 border-t-2 p-3 w-64 h-48')}>
<Text style={tailwind('text-center text-22fz')}>
{switchHeader}
</Text>
<View style={tailwind('flex-1 justify-center items-center')}>
<TouchableOpacity
style={tailwind('')}
onPress={() => setTheCurrentUnitSetting("metric")}
>
<Text style={tailwind('')}>
{metricUnits}
</Text>
</TouchableOpacity>
</View>
</View>
);
};
getWeather.js - Hook designed to fetch weather
import { useState, useEffect, useContext } from 'react';
import useLocation from '../hooks/useLocation.js';
import { CurrentUnitContext } from './CurrentUnitContext.js';
/**
* #getWeather - if permission was granted in useLocation() hook,
* uses Object theLocation containing lat. and long. coordinates as
* decimal number values. Returns the currentWeather. useEffect()
* relies on the value of Object theLocation.
*
* #WEATHER_API_KEY - accessed from clientSecret directory,
* hidden for repo security and must be manually provided.
* #currentWeather - useState hook to store JSON result of
* current weather data.
* #baseWeatherUrl - initial OpenWeatherMap API access string
* #weatherUrl - Full OpenWeatherMap API access string
*/
export default getWeather = () => {
const theLocation = useLocation();
const theCurrentUnit = useContext(CurrentUnitContext);
const [currentWeather, setCurrentWeather] = useState();
useEffect(() => {
const { WEATHER_API_KEY } = require("../clientSecret/openWeather.json");
let baseWeatherUrl = 'https://api.openweathermap.org/data/2.5/weather?',
weatherUrl = "";
//console.log(theLocation); //confirm we are getting location, uncomment if needed
if (theLocation !== undefined) {
weatherUrl = `${baseWeatherUrl}lat=${theLocation.latitude}&lon=${theLocation.longitude}&units=${theCurrentUnit}&appid=${WEATHER_API_KEY}`;
console.log(weatherUrl); //uncomment if needed
const fetchWeather = async () => {
try {
const response = await fetch(weatherUrl);
const result = await response.json();
if (response.ok) {
setCurrentWeather(result);
} else {
alert(result.message);
};
} catch (error) {
console.log(error);
alert(error.message);
} finally {
console.log("async function fetchWeather() has been run."); //API rate call confirmation
};
};
//API calls must not occur more than once every minute.
fetchWeather();
};
}, [theLocation, theCurrentUnit]);
return currentWeather;
};
As shown right now, the error message that appears is:
TypeError: undefined is not an object (evaluating >'_CurrentUnitContext.CurrentUnitContext.Provider')
This error is located at:
in App (created by ExpoRoot)
in ExpoRoot (at renderApplication.js:45)
in RCTView (at View.js:34)
in View (at AppContainer.js:106)
in RCTView (at View.js:34)
in View (at AppContainer.js:132)
in AppContainer (at renderApplication.js:39)
I was assisted, in CurrentUnitContext.js, changing
export default CurrentUnitContext = createContext();
to
export let CurrentUnitContext = createContext();
fixed the error.
I'm building a React app with redux-toolkit and I'm splitting my store into some slices with redux-toolkit's helper function createSlice.
Here it is a simple use case:
const sidebar = createSlice({
name: "sidebar",
initialState:
{
menus: {}, // Keep track of menus states (guid <-> open/close)
visible: true
},
reducers:
{
show(state, action)
{
state.visible = action.payload.visible;
},
setMenuOpen(state, action)
{
const { id, open } = action.payload;
state.menus[id] = open;
return state;
}
}
});
export default sidebar;
Everything works fine until I "add" actions (that change the store) to the slice but consider your team looking for an utility function "getMenuOpen": this method doesn't change the store (it's not an action and cannot be addeded to reducers object). You can of course read directly the data from the store (state.menus[<your_id>]) but consider a more complex example where manipulating the data requires some library imports, more complex code, etc...I want to modularize/hide each slice as much as possible.
Actually I'm using this workaround:
const sidebar = createSlice({ /* Same previous code... */ });
sidebar.methods =
{
getMenuOpen: (state, id) => state.menus[id]
};
export default sidebar;
The above code allows importing the slice from a component, mapStateToProps to the redux store, and invoke the utilty function getMenuOpen like this:
import sidebar from "./Sidebar.slice";
// Component declaration ...
const mapStateToProps = state => ({
sidebar: state.ui.layout.sidebar,
getMenuOpen(id)
{
return sidebar.methods.getMenuOpen(this.sidebar, id);
}
});
const mapDispatchToProps = dispatch => ({
setMenuOpen: (id, open) => dispatch(sidebar.actions.setMenuOpen({id, open}))
});
The ugly part is that I need to inject the slice node (this.sidebar) as fist param of getMenuOpen because it's not mapped (as for actions with reducers/actions) automatically from redux-toolkit.
So my question is: how can I clean my workaround in order to automatically map the store for utility functions? createSlice doesn't seem to support that but maybe some internal redux's api could help me in mapping my "slice.methods" automatically to the store.
Thanks
I habe problem with the "security" of angular 2. I try to calculate a span-witdh within a ngfor-loop:
<span style="width:updateStyle({{ ((date | amDifference : item.startdate : 'minutes' :true)/item.duration*100) | round }})% .....more span>
And import/changed a bypass-security.component from internet:
import { Component } from '#angular/core';
import { DomSanitizer, SafeStyle, SafeResourceUrl, SafeUrl } from '#angular/platform-browser';
#Component({
selector: 'bypass-security',
templateUrl: 'app/bypass-security.component.html',
})
export class BypassSecurityComponent {
dangerousUrl: string;
trustedUrl: SafeUrl;
dangerrousStyle: string;
trustedStyle: SafeStyle;
// #docregion trust-url
constructor(private sanitizer: DomSanitizer) {
// javascript: URLs are dangerous if attacker controlled.
// Angular sanitizes them in data binding, but you can
// explicitly tell Angular to trust this value:
this.dangerousUrl = 'javascript:alert("Hi there")';
this.trustedUrl = sanitizer.bypassSecurityTrustUrl(this.dangerousUrl);
this.trustedStyle = sanitizer.bypassSecurityTrustStyle(this.dangerrousStyle);
}
updateStyle(id: string)
{
this.trustedStyle = this.sanitizer.bypassSecurityTrustStyle(this.id);
}
}
But still get this error:
WARNING: sanitizing unsafe style value width:83%; height:3px; background-color:#d9d9d4; display:inline-block; (see http://g.co/ng/security#xss).
What can I do?
Thanks!
Calling methods from view bindings is usually bad practice because this method is called every time Angular2 checks for changes (runs change detection)
I don't really get what you try to accomplish, but if you want to set the width in % do something like
<span [style.width.%]="width">
and assign the value you want to be used for width to a property with that name
constructor() {
this.width = updateStyle(((date | amDifference : item.startdate : 'minutes' :true)/item.duration*100) | round }})
// I have a really hard time figuring out what this is supposed to do
I used the constructor only for an example. do the calculation whenever you think it should be updated (some event, some lifecycle callback, ...)