diff --git a/dash/_pages.py b/dash/_pages.py index be9d847309..8eaa258d75 100644 --- a/dash/_pages.py +++ b/dash/_pages.py @@ -380,24 +380,48 @@ def _path_to_page(path_id): return {}, None -def _page_meta_tags(app, request): - request_path = request.path - start_page, path_variables = _path_to_page(request_path.strip("/")) +def _get_image_url(app, request, image, supplied_image_url): + if supplied_image_url: + return supplied_image_url - image = start_page.get("image", "") if image: + if image.startswith(("http://", "https://")): + return image + image = app.get_asset_url(image) - assets_image_url = "".join([request.root, image.lstrip("/")]) if image else None - supplied_image_url = start_page.get("image_url") - image_url = supplied_image_url if supplied_image_url else assets_image_url + return "".join([request.root, image.lstrip("/")]) + + return None - title = start_page.get("title", app.title) - if callable(title): - title = title(**path_variables) if path_variables else title() - description = start_page.get("description", "") - if callable(description): - description = description(**path_variables) if path_variables else description() +def _page_meta_tags(app, request): + if not app.use_pages and not (app.description or app.image): + return [] + + title = app.title + description = app.description or "" + image = app.image + supplied_image_url = None + + if app.use_pages: + request_path = request.path + start_page, path_variables = _path_to_page(request_path.strip("/")) + + if start_page: + title = start_page.get("title", title) + description = start_page.get("description", description) + image = start_page.get("image") or image + supplied_image_url = start_page.get("image_url") + + if callable(title): + title = title(**path_variables) if path_variables else title() + + if callable(description): + description = ( + description(**path_variables) if path_variables else description() + ) + + image_url = _get_image_url(app, request, image, supplied_image_url) return [ {"name": "description", "content": description}, diff --git a/dash/dash.py b/dash/dash.py index ed1ed5ebc8..8e25108455 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -284,9 +284,23 @@ class Dash(ObsoleteChecker): to be True. Default `None`. :type use_pages: boolean - :param include_pages_meta: Include the page meta tags for twitter cards. + :param include_pages_meta: Include the meta tags for social media cards. :type include_pages_meta: bool + :param title: Default ``Dash``. Configures the document.title + (the text that appears in a browser tab). + + :param update_title: Default ``Updating...``. Configures the document.title + (the text that appears in a browser tab) text when a callback is being run. + Set to None or '' if you don't want the document.title to change or if you + want to control the document.title through a separate component or + clientside callback. + + :param description: Sets a default description for meta tags. + + :param image: The default image used for social media cards. Can be the + name of an image in the assets folder or an image URL. + :param assets_url_path: The local urls for assets will be: ``requests_pathname_prefix + assets_url_path + '/' + asset_path`` where ``asset_path`` is the path to a file inside ``assets_folder``. @@ -399,15 +413,6 @@ class Dash(ObsoleteChecker): be called after the Flask server is attached. :type plugins: list of objects - :param title: Default ``Dash``. Configures the document.title - (the text that appears in a browser tab). - - :param update_title: Default ``Updating...``. Configures the document.title - (the text that appears in a browser tab) text when a callback is being run. - Set to None or '' if you don't want the document.title to change or if you - want to control the document.title through a separate component or - clientside callback. - :param background_callback_manager: Background callback manager instance to support the ``@callback(..., background=True)`` decorator. One of ``DiskcacheManager`` or ``CeleryManager`` currently supported. @@ -425,8 +430,6 @@ class Dash(ObsoleteChecker): `routing_callback_inputs={"language": Input("language", "value")}` NOTE: the keys "pathname_" and "search_" are reserved for internal use. - :param description: Sets a default description for meta tags on Dash pages (use_pages=True). - :param on_error: Global callback error handler to call when an exception is raised. Receives the exception object as first argument. The callback_context can be used to access the original callback inputs, @@ -492,6 +495,10 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches eager_loading: bool = False, include_assets_files: bool = True, include_pages_meta: bool = True, + title: str = "Dash", + update_title: str = "Updating...", + description: Optional[str] = None, + image: Optional[str] = None, url_base_pathname: Optional[str] = None, requests_pathname_prefix: Optional[str] = None, routes_pathname_prefix: Optional[str] = None, @@ -506,15 +513,12 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches show_undo_redo: bool = False, extra_hot_reload_paths: Optional[Sequence[str]] = None, plugins: Optional[list] = None, - title: str = "Dash", - update_title: str = "Updating...", background_callback_manager: Optional[ Any ] = None, # Type should be specified if possible add_log_handler: bool = True, hooks: Optional[RendererHooks] = None, routing_callback_inputs: Optional[Dict[str, Union[Input, State]]] = None, - description: Optional[str] = None, on_error: Optional[Callable[[Exception], Any]] = None, use_async: Optional[bool] = None, health_endpoint: Optional[str] = None, @@ -612,6 +616,7 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches update_title=update_title, include_pages_meta=include_pages_meta, description=description, + image=image, health_endpoint=health_endpoint, hide_all_callbacks=False, csrf_token_name=csrf_token_name, @@ -645,6 +650,8 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches # keep title as a class property for backwards compatibility self.title = title + self.description = description + self.image = image # MCP (Model Context Protocol) configuration self._enable_mcp = get_combined_config("mcp_enabled", enable_mcp, False) @@ -1484,7 +1491,8 @@ def index(self, *_args, **_kwargs): # Refactored: direct access to global request adapter request = self.backend.request_adapter() - if self.use_pages and self.config.include_pages_meta and request: + # include social media meta tags + if self.config.include_pages_meta and request: metas = _page_meta_tags(self, request) + metas if self._favicon: