diff --git a/ansico-embed-url.css b/ansico-embed-url.css new file mode 100644 index 0000000..c1b8841 --- /dev/null +++ b/ansico-embed-url.css @@ -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; } \ No newline at end of file diff --git a/ansico-embed-url.js b/ansico-embed-url.js new file mode 100644 index 0000000..d8fb269 --- /dev/null +++ b/ansico-embed-url.js @@ -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 +); \ No newline at end of file diff --git a/ansico-embed-url.php b/ansico-embed-url.php new file mode 100644 index 0000000..2514f7a --- /dev/null +++ b/ansico-embed-url.php @@ -0,0 +1,111 @@ + '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, + ) ); +} +?> \ No newline at end of file