Upload files to "/"

This commit is contained in:
Andreas Andersen 2026-04-13 17:24:29 +00:00
parent 71a4031b20
commit f349647df4
3 changed files with 235 additions and 0 deletions

8
ansico-embed-url.css Normal file
View file

@ -0,0 +1,8 @@
.wp-block-ansico-embed-url { margin-bottom: 24px; }
.ansico-embed-input-container { padding: 20px; border: 2px dashed #ccc; background: #f9f9f9; border-radius: 8px; }
.ansico-embed-link { text-decoration: none !important; color: inherit !important; display: block; }
.ansico-embed-preview { border: 1px solid #dadde1; border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; background-color: #ffffff; }
.ansico-embed-image { width: 100%; height: auto; max-height: 300px; object-fit: cover; display: block; border-bottom: 1px solid #dadde1; margin: 0 !important; }
.ansico-embed-content { padding: 12px 16px; background-color: #f2f3f5; }
.ansico-embed-domain { margin: 0 0 4px 0 !important; font-size: 12px; color: #65676b; text-transform: uppercase; letter-spacing: 0.5px; }
.ansico-embed-title { margin: 0 !important; font-size: 16px; font-weight: 600; color: #050505; line-height: 1.3; }

116
ansico-embed-url.js Normal file
View file

@ -0,0 +1,116 @@
( function( blocks, element, editor, components, apiFetch ) {
var el = element.createElement;
var registerBlockType = blocks.registerBlockType;
var useBlockProps = editor.useBlockProps;
var BlockControls = editor.BlockControls;
var TextControl = components.TextControl;
var Button = components.Button;
var Spinner = components.Spinner;
var ToolbarGroup = components.ToolbarGroup;
var ToolbarButton = components.ToolbarButton;
var useState = element.useState;
registerBlockType( 'ansico/embed-url', {
title: 'Ansico Embed URL',
icon: 'admin-links',
category: 'embed',
attributes: {
url: { type: 'string' },
title: { type: 'string' },
imageUrl: { type: 'string' },
domain: { type: 'string' },
isEditing: { type: 'boolean', default: true }
},
edit: function( props ) {
var attributes = props.attributes;
var setAttributes = props.setAttributes;
var isFetching = useState( false );
var setIsFetching = isFetching[1];
var blockProps = useBlockProps();
function fetchUrlData() {
if ( ! attributes.url ) return;
setIsFetching( true );
apiFetch( {
path: '/ansico/v1/fetch-url',
method: 'POST',
data: { url: attributes.url }
} ).then( function( response ) {
setAttributes( {
title: response.title,
imageUrl: response.image,
domain: response.domain,
isEditing: false
} );
setIsFetching( false );
} ).catch( function( error ) {
alert( 'Error fetching data.' );
setIsFetching( false );
} );
}
if ( attributes.isEditing ) {
return el( 'div', blockProps,
el( 'div', { className: 'ansico-embed-input-container' },
el( TextControl, {
label: 'Enter article URL',
value: attributes.url,
onChange: function( val ) { setAttributes( { url: val } ); },
placeholder: 'https://...'
} ),
el( Button, {
isPrimary: true,
onClick: fetchUrlData,
disabled: isFetching[0] || ! attributes.url
}, isFetching[0] ? el( Spinner ) : 'Generate Embed' )
)
);
}
return el( 'div', blockProps,
el( BlockControls, {},
el( ToolbarGroup, {},
el( ToolbarButton, {
icon: 'edit',
label: 'Edit URL',
onClick: function() { setAttributes( { isEditing: true } ); }
} )
)
),
el( 'div', { className: 'ansico-embed-preview' },
attributes.imageUrl && el( 'img', { src: attributes.imageUrl, className: 'ansico-embed-image' } ),
el( 'div', { className: 'ansico-embed-content' },
el( 'p', { className: 'ansico-embed-domain' }, attributes.domain ),
el( 'h3', { className: 'ansico-embed-title' }, attributes.title )
)
)
);
},
save: function( props ) {
var attributes = props.attributes;
var blockProps = useBlockProps.save();
if ( ! attributes.url || attributes.isEditing ) return null;
return el( 'div', blockProps,
el( 'a', { href: attributes.url, className: 'ansico-embed-link', target: '_blank', rel: 'noopener noreferrer' },
el( 'div', { className: 'ansico-embed-preview' },
attributes.imageUrl && el( 'img', { src: attributes.imageUrl, className: 'ansico-embed-image' } ),
el( 'div', { className: 'ansico-embed-content' },
el( 'p', { className: 'ansico-embed-domain' }, attributes.domain ),
el( 'h3', { className: 'ansico-embed-title' }, attributes.title )
)
)
)
);
}
} );
} )(
window.wp.blocks,
window.wp.element,
window.wp.blockEditor,
window.wp.components,
window.wp.apiFetch
);

111
ansico-embed-url.php Normal file
View file

@ -0,0 +1,111 @@
<?php
/**
* Plugin Name: Ansico Embed URL
* Description: A Gutenberg block to embed a URL with a social-media style preview card. Automatically fetches metadata and saves the image locally.
* Version: 1.0.0
* Author: Ansico
* Text Domain: ansico-embed-url
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Register the block and its assets
add_action( 'init', 'ansico_embed_url_register_block' );
function ansico_embed_url_register_block() {
wp_register_script(
'ansico-embed-url-script',
plugins_url( 'ansico-embed-url.js', __FILE__ ),
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-api-fetch' ),
'1.0.0'
);
wp_register_style(
'ansico-embed-url-style',
plugins_url( 'ansico-embed-url.css', __FILE__ ),
array(),
'1.0.0'
);
register_block_type( 'ansico/embed-url', array(
'editor_script' => 'ansico-embed-url-script',
'editor_style' => 'ansico-embed-url-style',
'style' => 'ansico-embed-url-style',
) );
}
// Register REST API Route
add_action( 'rest_api_init', function () {
register_rest_route( 'ansico/v1', '/fetch-url', array(
'methods' => 'POST',
'callback' => 'ansico_embed_url_fetch_metadata',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
}
) );
} );
function ansico_embed_url_fetch_metadata( $request ) {
$url = esc_url_raw( $request->get_param( 'url' ) );
if ( empty( $url ) ) {
return new WP_Error( 'missing_url', 'No URL provided', array( 'status' => 400 ) );
}
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'fetch_failed', 'Could not fetch URL', array( 'status' => 500 ) );
}
$html = wp_remote_retrieve_body( $response );
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) );
libxml_clear_errors();
$title = '';
$image_url = '';
$domain = parse_url( $url, PHP_URL_HOST );
$domain = str_replace( 'www.', '', $domain );
$tags = $doc->getElementsByTagName( 'meta' );
foreach ( $tags as $tag ) {
if ( $tag->getAttribute( 'property' ) === 'og:title' ) {
$title = $tag->getAttribute( 'content' );
}
if ( $tag->getAttribute( 'property' ) === 'og:image' ) {
$image_url = $tag->getAttribute( 'content' );
}
}
if ( empty( $title ) ) {
$nodes = $doc->getElementsByTagName( 'title' );
if ( $nodes->length > 0 ) {
$title = $nodes->item(0)->nodeValue;
}
}
$local_image_url = '';
if ( ! empty( $image_url ) ) {
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
$sideloaded = media_sideload_image( $image_url, 0, null, 'src' );
if ( ! is_wp_error( $sideloaded ) ) {
$local_image_url = $sideloaded;
} else {
$local_image_url = $image_url;
}
}
return rest_ensure_response( array(
'title' => $title,
'image' => $local_image_url,
'domain' => $domain,
) );
}
?>