晋太元中,武陵人捕鱼为业。缘溪行,忘路之远近。忽逢桃花林,夹岸数百步,中无杂树,芳草鲜美,落英缤纷。渔人甚异之,复前行,欲穷其林。 林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨然,有良田、美池、桑竹之属。阡陌交通,鸡犬相闻。其中往来种作,男女衣着,悉如外人。黄发垂髫,并怡然自乐。 见渔人,乃大惊,问所从来。具答之。便要还家,设酒杀鸡作食。村中闻有此人,咸来问讯。自云先世避秦时乱,率妻子邑人来此绝境,不复出焉,遂与外人间隔。问今是何世,乃不知有汉,无论魏晋。此人一一为具言所闻,皆叹惋。余人各复延至其家,皆出酒食。停数日,辞去。此中人语云:“不足为外人道也。”(间隔 一作:隔绝) 既出,得其船,便扶向路,处处志之。及郡下,诣太守,说如此。太守即遣人随其往,寻向所志,遂迷,不复得路。 南阳刘子骥,高尚士也,闻之,欣然规往。未果,寻病终。后遂无问津者。
|
Server : Apache System : Linux srv.rainic.com 4.18.0-553.47.1.el8_10.x86_64 #1 SMP Wed Apr 2 05:45:37 EDT 2025 x86_64 User : rainic ( 1014) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/stando/public_html/wp-content/plugins/pretty-link/js/editor/components/url-input/ |
Upload File : |
/**
* External dependencies
*/
import { throttle, map, pick, defaultTo } from 'lodash';
import classnames from 'classnames';
import scrollIntoView from 'dom-scroll-into-view';
/**
* WordPress dependencies
*/
import { __, sprintf, _n } from '@wordpress/i18n';
import { Component, createRef } from '@wordpress/element';
import { UP, DOWN, ENTER, TAB } from '@wordpress/keycodes';
import { Spinner, withSpokenMessages, Popover } from '@wordpress/components';
import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
import { decodeEntities } from '@wordpress/html-entities';
// Since URLInput is rendered in the context of other inputs, but should be
// considered a separate modal node, prevent keyboard events from propagating
// as being considered from the input.
const stopEventPropagation = ( event ) => event.stopPropagation();
const fetchLinkSuggestions = async ( search ) => {
const links = await apiFetch( {
url: addQueryArgs( ajaxurl, {
action: 'prli_search_for_links',
term: search,
} ),
} );
return map( links, ( link ) => ( {
url: link.pretty_url,
title: decodeEntities( link.value ) + ' (' + decodeEntities( link.slug ) + ')' || __( '(no title)' ),
} ) );
};
class URLInput extends Component {
constructor( { autocompleteRef } ) {
super( ...arguments );
this.onChange = this.onChange.bind( this );
this.onKeyDown = this.onKeyDown.bind( this );
this.autocompleteRef = autocompleteRef || createRef();
this.inputRef = createRef();
this.updateSuggestions = throttle( this.updateSuggestions.bind( this ), 200 );
this.suggestionNodes = [];
this.state = {
suggestions: [],
showSuggestions: false,
selectedSuggestion: null,
};
}
componentDidUpdate() {
const { showSuggestions, selectedSuggestion } = this.state;
// only have to worry about scrolling selected suggestion into view
// when already expanded
if ( showSuggestions && selectedSuggestion !== null && ! this.scrollingIntoView ) {
this.scrollingIntoView = true;
scrollIntoView( this.suggestionNodes[ selectedSuggestion ], this.autocompleteRef.current, {
onlyScrollIfNeeded: true,
} );
this.props.setTimeout( () => {
this.scrollingIntoView = false;
}, 100 );
}
}
componentWillUnmount() {
delete this.suggestionsRequest;
}
bindSuggestionNode( index ) {
return ( ref ) => {
this.suggestionNodes[ index ] = ref;
};
}
updateSuggestions( value ) {
// Show the suggestions after typing at least 2 characters
// and also for URLs
if ( value.length < 2 || /^https?:/.test( value ) ) {
this.setState( {
showSuggestions: false,
selectedSuggestion: null,
loading: false,
} );
return;
}
this.setState( {
showSuggestions: true,
selectedSuggestion: null,
loading: true,
} );
const request = fetchLinkSuggestions( value );
request.then( ( suggestions ) => {
// A fetch Promise doesn't have an abort option. It's mimicked by
// comparing the request reference in on the instance, which is
// reset or deleted on subsequent requests or unmounting.
if ( this.suggestionsRequest !== request ) {
return;
}
this.setState( {
suggestions,
loading: false,
} );
if ( !! suggestions.length ) {
this.props.debouncedSpeak( sprintf( _n(
'%d result found, use up and down arrow keys to navigate.',
'%d results found, use up and down arrow keys to navigate.',
suggestions.length
), suggestions.length ), 'assertive' );
} else {
this.props.debouncedSpeak( __( 'No results.' ), 'assertive' );
}
} ).catch( () => {
if ( this.suggestionsRequest === request ) {
this.setState( {
loading: false,
} );
}
} );
this.suggestionsRequest = request;
}
onChange( event ) {
const inputValue = event.target.value;
this.props.onChange( inputValue );
this.updateSuggestions( inputValue );
}
onKeyDown( event ) {
const { showSuggestions, selectedSuggestion, suggestions, loading } = this.state;
// If the suggestions are not shown or loading, we shouldn't handle the arrow keys
// We shouldn't preventDefault to allow block arrow keys navigation
if ( ! showSuggestions || ! suggestions.length || loading ) {
// In the Windows version of Firefox the up and down arrows don't move the caret
// within an input field like they do for Mac Firefox/Chrome/Safari. This causes
// a form of focus trapping that is disruptive to the user experience. This disruption
// only happens if the caret is not in the first or last position in the text input.
// See: https://github.com/WordPress/gutenberg/issues/5693#issuecomment-436684747
switch ( event.keyCode ) {
// When UP is pressed, if the caret is at the start of the text, move it to the 0
// position.
case UP: {
if ( 0 !== event.target.selectionStart ) {
event.stopPropagation();
event.preventDefault();
// Set the input caret to position 0
event.target.setSelectionRange( 0, 0 );
}
break;
}
// When DOWN is pressed, if the caret is not at the end of the text, move it to the
// last position.
case DOWN: {
if ( this.props.value.length !== event.target.selectionStart ) {
event.stopPropagation();
event.preventDefault();
// Set the input caret to the last position
event.target.setSelectionRange( this.props.value.length, this.props.value.length );
}
break;
}
}
return;
}
const suggestion = this.state.suggestions[ this.state.selectedSuggestion ];
switch ( event.keyCode ) {
case UP: {
event.stopPropagation();
event.preventDefault();
const previousIndex = ! selectedSuggestion ? suggestions.length - 1 : selectedSuggestion - 1;
this.setState( {
selectedSuggestion: previousIndex,
} );
break;
}
case DOWN: {
event.stopPropagation();
event.preventDefault();
const nextIndex = selectedSuggestion === null || ( selectedSuggestion === suggestions.length - 1 ) ? 0 : selectedSuggestion + 1;
this.setState( {
selectedSuggestion: nextIndex,
} );
break;
}
case TAB: {
if ( this.state.selectedSuggestion !== null ) {
this.selectLink( suggestion );
// Announce a link has been selected when tabbing away from the input field.
this.props.speak( __( 'Link selected.' ) );
}
break;
}
case ENTER: {
if ( this.state.selectedSuggestion !== null ) {
event.stopPropagation();
this.selectLink( suggestion );
}
break;
}
}
}
selectLink( suggestion ) {
this.props.onChange( suggestion.url, suggestion );
this.setState( {
selectedSuggestion: null,
showSuggestions: false,
} );
}
handleOnClick( suggestion ) {
this.selectLink( suggestion );
// Move focus to the input field when a link suggestion is clicked.
this.inputRef.current.focus();
}
render() {
const { value = '', autoFocus = true, instanceId, className } = this.props;
const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state;
const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`;
const suggestionOptionIdPrefix = `block-editor-url-input-suggestion-${ instanceId }`;
/* eslint-disable jsx-a11y/no-autofocus */
return (
<div className={ classnames( 'editor-url-input block-editor-url-input', className ) }>
<input
autoFocus={ autoFocus }
type="text"
aria-label={ __( 'URL' ) }
required
value={ value }
onChange={ this.onChange }
onInput={ stopEventPropagation }
placeholder={ __( 'Paste or type to search for your Pretty Link' ) }
onKeyDown={ this.onKeyDown }
role="combobox"
aria-expanded={ showSuggestions }
aria-autocomplete="list"
aria-owns={ suggestionsListboxId }
aria-activedescendant={ selectedSuggestion !== null ? `${ suggestionOptionIdPrefix }-${ selectedSuggestion }` : undefined }
ref={ this.inputRef }
/>
{ ( loading ) && <Spinner /> }
{ showSuggestions && !! suggestions.length &&
<Popover position="bottom" noArrow focusOnMount={ false }>
<div
className="editor-url-input__suggestions block-editor-url-input__suggestions"
id={ suggestionsListboxId }
ref={ this.autocompleteRef }
role="listbox"
>
{ suggestions.map( ( suggestion, index ) => (
<button
key={ suggestion.id }
role="option"
tabIndex="-1"
id={ `${ suggestionOptionIdPrefix }-${ index }` }
ref={ this.bindSuggestionNode( index ) }
className={ classnames( 'editor-url-input__suggestion block-editor-url-input__suggestion', {
'is-selected': index === selectedSuggestion,
} ) }
onClick={ () => this.handleOnClick( suggestion ) }
aria-selected={ index === selectedSuggestion }
>
{ suggestion.title }
</button>
) ) }
</div>
</Popover>
}
</div>
);
/* eslint-enable jsx-a11y/no-autofocus */
}
}
/**
* @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/url-input/README.md
*/
export default compose(
withSafeTimeout,
withSpokenMessages,
withInstanceId,
withSelect( ( select ) => {
const { getSettings } = select( 'core/block-editor' );
return {
fetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions,
};
} )
)( URLInput );