Spaces:
Running
Running
| from dataclasses import dataclass, field | |
| import mesop as me | |
| _INTRO_TEXT = """ | |
| # Mesop Markdown Editor Example | |
| This example shows how to make a simple markdown editor. | |
| """.strip() | |
| class Note: | |
| """Content of note.""" | |
| content: str = "" | |
| class State: | |
| notes: list[Note] = field(default_factory=lambda: [Note(content=_INTRO_TEXT)]) | |
| selected_note_index: int = 0 | |
| selected_note_content: str = _INTRO_TEXT | |
| show_preview: bool = True | |
| def load(e: me.LoadEvent): | |
| me.set_theme_mode("system") | |
| def page(): | |
| state = me.state(State) | |
| with me.box(style=_style_container(state.show_preview)): | |
| # Note list column | |
| with me.box(style=_STYLE_NOTES_NAV): | |
| # Toolbar | |
| with me.box(style=_STYLE_TOOLBAR): | |
| with me.content_button(on_click=on_click_new): | |
| with me.tooltip(message="New note"): | |
| me.icon(icon="add_notes") | |
| with me.content_button(on_click=on_click_hide): | |
| with me.tooltip( | |
| message="Hide preview" if state.show_preview else "Show preview" | |
| ): | |
| me.icon(icon="hide_image") | |
| # Note list | |
| for index, note in enumerate(state.notes): | |
| with me.box( | |
| key=f"note-{index}", | |
| on_click=on_click_note, | |
| style=_style_note_row(index == state.selected_note_index), | |
| ): | |
| me.text(_render_note_excerpt(note.content)) | |
| # Markdown Editor Column | |
| with me.box(style=_STYLE_EDITOR): | |
| me.native_textarea( | |
| value=state.selected_note_content, | |
| style=_STYLE_TEXTAREA, | |
| on_input=on_text_input, | |
| ) | |
| # Markdown Preview Column | |
| if state.show_preview: | |
| with me.box(style=_STYLE_PREVIEW): | |
| if state.selected_note_index < len(state.notes): | |
| me.markdown(state.notes[state.selected_note_index].content) | |
| # HELPERS | |
| _EXCERPT_CHAR_LIMIT = 90 | |
| def _render_note_excerpt(content: str) -> str: | |
| if len(content) <= _EXCERPT_CHAR_LIMIT: | |
| return content | |
| return content[:_EXCERPT_CHAR_LIMIT] + "..." | |
| # EVENT HANDLERS | |
| def on_click_new(e: me.ClickEvent): | |
| state = me.state(State) | |
| # Need to update the initial value of the editor text area so we can | |
| # trigger a diff to reset the editor to empty. Need to yield this change. | |
| # for this to work. | |
| state.selected_note_content = state.notes[state.selected_note_index].content | |
| yield | |
| # Reset the initial value of the editor text area to empty since the new note | |
| # has no content. | |
| state.selected_note_content = "" | |
| state.notes.append(Note()) | |
| state.selected_note_index = len(state.notes) - 1 | |
| yield | |
| def on_click_hide(e: me.ClickEvent): | |
| """Hides/Shows preview Markdown pane.""" | |
| state = me.state(State) | |
| state.show_preview = bool(not state.show_preview) | |
| def on_click_note(e: me.ClickEvent): | |
| """Selects a note from the note list.""" | |
| state = me.state(State) | |
| note_id = int(e.key.replace("note-", "")) | |
| note = state.notes[note_id] | |
| state.selected_note_index = note_id | |
| state.selected_note_content = note.content | |
| def on_text_input(e: me.InputEvent): | |
| """Captures text in editor.""" | |
| state = me.state(State) | |
| state.notes[state.selected_note_index].content = e.value | |
| # STYLES | |
| _BACKGROUND_COLOR = me.theme_var("surface-container-lowest") | |
| _FONT_COLOR = me.theme_var("on-surface-variant") | |
| _NOTE_ROW_FONT_COLOR = me.theme_var("on-surface") | |
| _NOTE_ROW_FONT_SIZE = "14px" | |
| _SELECTED_ROW_BACKGROUND_COLOR = me.theme_var("surface-variant") | |
| _DEFAULT_BORDER_STYLE = me.BorderSide( | |
| width=1, style="solid", color=me.theme_var("outline-variant") | |
| ) | |
| def _style_container(show_preview: bool = True) -> me.Style: | |
| return me.Style( | |
| background=_BACKGROUND_COLOR, | |
| color=_FONT_COLOR, | |
| display="grid", | |
| grid_template_columns="2fr 4fr 4fr" if show_preview else "2fr 8fr", | |
| height="100vh", | |
| ) | |
| def _style_note_row(selected: bool = False) -> me.Style: | |
| return me.Style( | |
| color=_NOTE_ROW_FONT_COLOR, | |
| font_size=_NOTE_ROW_FONT_SIZE, | |
| background=_SELECTED_ROW_BACKGROUND_COLOR if selected else "none", | |
| padding=me.Padding.all(10), | |
| border=me.Border(bottom=_DEFAULT_BORDER_STYLE), | |
| height="100px", | |
| overflow_x="hidden", | |
| overflow_y="hidden", | |
| ) | |
| _STYLE_NOTES_NAV = me.Style(overflow_y="scroll", padding=me.Padding.all(15)) | |
| _STYLE_TOOLBAR = me.Style( | |
| padding=me.Padding.all(5), | |
| border=me.Border(bottom=_DEFAULT_BORDER_STYLE), | |
| ) | |
| _STYLE_EDITOR = me.Style( | |
| overflow_y="hidden", | |
| padding=me.Padding(left=20, right=15, top=20, bottom=0), | |
| border=me.Border( | |
| left=_DEFAULT_BORDER_STYLE, | |
| right=_DEFAULT_BORDER_STYLE, | |
| ), | |
| ) | |
| _STYLE_PREVIEW = me.Style( | |
| overflow_y="scroll", padding=me.Padding.symmetric(vertical=0, horizontal=20) | |
| ) | |
| _STYLE_TEXTAREA = me.Style( | |
| color=_FONT_COLOR, | |
| background=_BACKGROUND_COLOR, | |
| outline="none", # Hides focus border | |
| border=me.Border.all(me.BorderSide(style="none")), | |
| width="100%", | |
| height="100%", | |
| ) | |