瀏覽代碼

doc: update doc-demo

wangweimin 4 年之前
父節點
當前提交
26b6ded0c6
共有 7 個文件被更改,包括 288 次插入114 次删除
  1. 13 6
      demos/doc_demo.py
  2. 3 2
      docs/_ext/README.md
  3. 26 5
      docs/_ext/codeblock.py
  4. 45 33
      docs/guide.rst
  5. 5 1
      docs/static/pywebio.js
  6. 64 26
      pywebio/input.py
  7. 132 41
      pywebio/output.py

+ 13 - 6
demos/doc_demo.py

@@ -13,12 +13,12 @@ here_dir = path.dirname(path.abspath(__file__))
 
 
 def gen_snippets(code):
+    code = code.replace('# ..demo-only', '')
+    code = '\n'.join(i for i in code.splitlines() if '# ..doc-only' not in i)
+
     parts = code.split('\n## ----\n')
     for p in parts:
-        p = p.replace('\n## ', '\n')
-        p = p.replace('\n##\n', '\n\n')
-
-        yield p.lstrip('## ').lstrip('##').strip('\n')
+        yield p.strip('\n')
 
 
 def run_code(code, scope):
@@ -26,6 +26,13 @@ def run_code(code, scope):
         exec(code, globals())
 
 
+IMPORT_CODE = """from pywebio.input import *
+from pywebio.output import *
+from pywebio.session import *
+
+"""
+
+
 def copytoclipboard(code):
     run_js("navigator.clipboard.writeText(text)", text=code)
     toast('已复制')
@@ -41,7 +48,7 @@ def handle_code(code, title):
 
             put_buttons(['运行', '复制代码'], onclick=[
                 partial(run_code, code=p, scope=scope),
-                partial(copytoclipboard, code=p)
+                partial(copytoclipboard, code=IMPORT_CODE + p)
             ])
 
         put_markdown('----')
@@ -53,7 +60,7 @@ def get_app():
     app = {}
     try:
         demos = listdir(path.join(here_dir, 'doc_domes'))
-    except:
+    except Exception:
         demos = []
 
     demo_infos = []

+ 3 - 2
docs/_ext/README.md

@@ -28,5 +28,6 @@ CODE_EXPORT_PATH=/Users/wangweimin/repos/PyWebIO/demos/doc_domes make clean html
 
 特殊注释如下:
 
- - `## ----` : 表示分割示例代码,将示例代码分割成不同的部分来分别运行
- - `## ...` : 表示在运行示例代码时附加运行的代码
+ - `## ----` : 表示分割示例代码,将示例代码分割成不同的部分来分别运行。该注释主要放到行首
+ - `# ..demo-only` : 表示该行代码仅在Demo页面中显示 
+ - `# ..doc-only` : 表示该行代码仅在文档中显示

+ 26 - 5
docs/_ext/codeblock.py

@@ -2,6 +2,7 @@ import os
 
 from docutils.parsers.rst import directives
 from sphinx.directives.code import CodeBlock
+import hashlib
 
 
 class ExportableCodeBlock(CodeBlock):
@@ -10,6 +11,14 @@ class ExportableCodeBlock(CodeBlock):
         'name': directives.unchanged,
     }
 
+    names = set()
+
+    @staticmethod
+    def md5(str_data):
+        t = hashlib.md5()
+        t.update(str_data.encode('utf-8'))
+        return t.hexdigest()
+
     def run(self):
         code_save_path = os.environ.get('CODE_EXPORT_PATH')
         caption = self.options.get('summary', '')
@@ -17,19 +26,31 @@ class ExportableCodeBlock(CodeBlock):
         if code_save_path and not os.path.exists(code_save_path):
             os.mkdir(code_save_path)
 
+        code_id = self.md5('\n'.join(self.content))[-5:]
         if self.options.get('name', None) is None:
             # 设置name属性,从而让生成的代码html块具有id属性
-            self.options.update({'name': 'demo'})
+            self.options.update({'name': 'demo-' + code_id})
 
+        name = self.options.get('name').replace('_','-')
+        if name in type(self).names:
+            name += '-' + code_id
+            self.options.update({'name': name})
+        else:
+            type(self).names.add(name)
+
+        # 设置特殊class值,用于在js中搜索
         classes = self.options.get('class', [])
         classes.append('demo-cb')
         self.options.update({'class': classes})
-        content_text = '\n'.join(self.content)
+
+        raw_content_text = '\n'.join(self.content)
 
         content, self.content = self.content, []
         for c in content:
-            if not c.startswith('## ') and c != '##':
-                self.content.append(c)
+            if '..demo-only' in c or '## ----' in c:
+                continue
+            c = c.replace('# ..doc-only', '')
+            self.content.append(c)
 
         nodes = super().run()
 
@@ -40,7 +61,7 @@ class ExportableCodeBlock(CodeBlock):
 
         if code_save_path and elem_id:
             fpath = os.path.join(code_save_path, elem_id)
-            open(fpath, 'w').write(caption + '\n\n' + content_text)
+            open(fpath, 'w').write(caption + '\n\n' + raw_content_text)
 
         return nodes
 

+ 45 - 33
docs/guide.rst

@@ -29,7 +29,7 @@ User's guide
     :summary: 文本输入
 
     age = input("How old are you?", type=NUMBER)
-    ## put_text('age = %r' % age)
+    put_text('age = %r' % age)  # ..demo-only
 
 这样一行代码的效果为:浏览器会弹出一个文本输入框来获取输入,在用户输入完成将表单提交后,``input`` 函数返回用户输入的值。
 
@@ -41,27 +41,32 @@ User's guide
 
     # 密码输入
     password = input("Input password", type=PASSWORD)
-    ## put_text('password = %r' % password)
+    put_text('password = %r' % password)  # ..demo-only
     ## ----
 
     # 下拉选择框
     gift = select('Which gift you want?', ['keyboard', 'ipad'])
-    ## put_text('gift = %r' % gift)
+    put_text('gift = %r' % gift)  # ..demo-only
     ## ----
 
-    # CheckBox
+    # 勾选选项
     agree = checkbox("用户协议", options=['I agree to terms and conditions'])
-    ## put_text('agree = %r' % agree)
+    put_text('agree = %r' % agree)  # ..demo-only
     ## ----
 
-    # Text Area
+    # 单选选项
+    answer = radio("Choose one", options=['A', 'B', 'C', 'D'])
+    put_text('answer = %r' % answer)  # ..demo-only
+    ## ----
+
+    # 多行文本输入
     text = textarea('Text Area', rows=3, placeholder='Some text')
-    ## put_text('text = %r' % text)
+    put_text('text = %r' % text)  # ..demo-only
     ## ----
 
     # 文件上传
     img = file_upload("Select a image:", accept="image/*")
-    ## put_text('img = %r' % img)
+    put_text('img = %r' % img)  # ..demo-only
 
 
 输入选项
@@ -93,7 +98,7 @@ User's guide
             return 'Too old!!'
 
     age = input("How old are you?", type=NUMBER, valid_func=check_age)
-    ## put_text('age = %r' % age)
+    put_text('age = %r' % age)  # ..demo-only
 
 当用户输入了不合法的值时,页面上的显示如下:
 
@@ -110,7 +115,7 @@ User's guide
         'mode': "python",  # 编辑区代码语言
         'theme': 'darcula',  # 编辑区darcula主题, Visit https://codemirror.net/demo/theme.html#cobalt to get more themes
     }, value='import something\n# Write your python code')
-    ## put_code(code, language='python')
+    put_code(code, language='python')  # ..demo-only
 
 文本框的显示效果为:
 
@@ -127,12 +132,12 @@ PyWebIO支持输入组, 返回结果为一个字典。`pywebio.input.input_group
     :name: input-group
     :summary: 输入组
 
-    ## def check_age(p):  # 检验函数校验通过时返回None,否则返回错误消息
-    ##     if p < 10:
-    ##         return 'Too young!!'
-    ##     if p > 60:
-    ##         return 'Too old!!'
-    ##
+    def check_age(p):  # 检验函数校验通过时返回None,否则返回错误消息  # ..demo-only
+        if p < 10:                  # ..demo-only
+            return 'Too young!!'    # ..demo-only
+        if p > 60:                  # ..demo-only
+            return 'Too old!!'      # ..demo-only
+                                    # ..demo-only
     data = input_group("Basic info",[
       input('Input your name', name='name'),
       input('Input your age', name='age', type=NUMBER, valid_func=check_age)
@@ -145,23 +150,23 @@ PyWebIO支持输入组, 返回结果为一个字典。`pywebio.input.input_group
     :name: input-group
     :summary: 输入组
 
-    ## def check_age(p):  # 检验函数校验通过时返回None,否则返回错误消息
-    ##     if p < 10:
-    ##         return 'Too young!!'
-    ##     if p > 60:
-    ##         return 'Too old!!'
-    ##
+    def check_age(p):  # 检验函数校验通过时返回None,否则返回错误消息  # ..demo-only
+        if p < 10:                  # ..demo-only
+            return 'Too young!!'    # ..demo-only
+        if p > 60:                  # ..demo-only
+            return 'Too old!!'      # ..demo-only
+                                    # ..demo-only
     def check_form(data):  # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
         if len(data['name']) > 6:
             return ('name', '名字太长!')
         if data['age'] <= 0:
             return ('age', '年龄不能为负数!')
 
-    ## data = input_group("Basic info",[
-    ##    input('Input your name', name='name'),
-    ##    input('Input your age', name='age', type=NUMBER, valid_func=check_age)
-    ## ], valid_func=check_form)
-    ## put_text(data['name'], data['age'])
+    data = input_group("Basic info",[           # ..demo-only
+       input('Input your name', name='name'),   # ..demo-only
+       input('Input your age', name='age', type=NUMBER, valid_func=check_age)  # ..demo-only
+    ], valid_func=check_form)              # ..demo-only
+    put_text(data['name'], data['age'])    # ..demo-only
 
 .. attention::
    PyWebIO 根据是否在输入函数中传入 ``name`` 参数来判断输入函数是在 `input_group` 中还是被单独调用。
@@ -223,9 +228,10 @@ PyWebIO提供的全部输出函数见 :doc:`pywebio.output </output>` 模块。
         ['Type', 'Content'],
         ['html', put_html('X<sup>2</sup>')],
         ['text', '<hr/>'],  # 等价于 ['text', put_text('<hr/>')]
-        ['buttons', put_buttons(['A', 'B'], onclick=put_text)],
+        ['buttons', put_buttons(['A', 'B'], onclick=...)],  # ..doc-only
+        ['buttons', put_buttons(['A', 'B'], onclick=put_text)],  # ..dome-only
         ['markdown', put_markdown('`Awesome PyWebIO!`')],
-        ['file', put_file('hello.text', b'')],
+        ['file', put_file('hello.text', b'hello world')],
         ['table', put_table([['A', 'B'], ['C', 'D']])]
     ])
 
@@ -370,16 +376,16 @@ PyWebIO使用Scope模型来对内容输出的位置进行灵活地控制,PyWeb
     :name: use-scope-decorator
     :summary: `use_scope()`作为装饰器来使用
 
-    ## import time
+    import time  # ..demo-only
     from datetime import datetime
 
     @use_scope('time', clear=True)
     def show_time():
         put_text(datetime.now())
 
-    ## while 1:
-    ##    show_time()
-    ##    time.sleep(1)
+    while 1:          # ..demo-only
+       show_time()    # ..demo-only
+       time.sleep(1)  # ..demo-only
 
 第一次调用 ``show_time`` 时,将会在当前位置创建 ``time`` 输出域并在其中输出当前时间,之后每次调用 ``show_time()`` ,时间都会输出到相同的区域。
 
@@ -451,8 +457,14 @@ PyWebIO使用Scope栈来保存运行时的Scope的嵌套层级。
 
     with use_scope('scope1'):
         put_text('A')               # 输出内容: A
+    ## ----
+    with use_scope('scope1'):  # ..demo-only
         put_text('B', position=0)   # 输出内容: B A
+    ## ----
+    with use_scope('scope1'):  # ..demo-only
         put_text('C', position=-2)  # 输出内容: B C A
+    ## ----
+    with use_scope('scope1'):  # ..demo-only
         put_text('D', position=1)   # 输出内容: B D C A
 
 **输出域控制函数**

+ 5 - 1
docs/static/pywebio.js

@@ -1,4 +1,8 @@
-const DEMO_URL = 'http://pywebio-demos.wangweimin.site/?pywebio_api=doc_demo';
+let DEMO_URL;
+if (localStorage.getItem('pywebio_doc_demo_url'))
+    DEMO_URL = localStorage.getItem('pywebio_doc_demo_url');
+else
+    DEMO_URL = 'http://pywebio-demos.wangweimin.site/?pywebio_api=doc_demo';
 
 var parseHTML = function (str) {
     let tmp = document.implementation.createHTMLDocument();

+ 64 - 26
pywebio/input.py

@@ -112,7 +112,11 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, action
 
        其中 `DATE` , `TIME` 类型在某些浏览器上不被支持,详情见 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Browser_compatibility
     :param Callable valid_func: 输入值校验函数. 如果提供,当用户输入完毕或提交表单后校验函数将被调用.
-        ``valid_func`` 接收输入值作为参数,当输入值有效时,返回 ``None`` ,当输入值无效时,返回错误提示字符串. 比如::
+        ``valid_func`` 接收输入值作为参数,当输入值有效时,返回 ``None`` ,当输入值无效时,返回错误提示字符串. 比如:
+
+        .. exportable-codeblock::
+            :name: input-valid-func
+            :summary: `input()` 输入值校验
 
             def check_age(age):
                 if age>30:
@@ -141,14 +145,19 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, action
 
         双参数调用的使用场景为:表单项的值通过回调动态生成,同时希望用户表单显示的和实际提交的数据不同(例如表单项上可以显示更人性化的内容,而表单项的值则可以保存更方便被处理的对象)
 
-        使用示例::
+        使用示例:
+
+        .. exportable-codeblock::
+            :name: input-action
+            :summary: `input()`使用action参数动态设置表单项的值
 
             import time
             def set_now_ts(set_value):
                 set_value(int(time.time()))
 
-            input('Timestamp', type=NUMBER, action=('Now', set_now_ts))
-
+            ts = input('Timestamp', type=NUMBER, action=('Now', set_now_ts))
+            put_text('Timestamp:', ts)  # ..demo-only
+            ## ----
             from datetime import date,timedelta
             def select_date(set_value):
                 with popup('Select Date'):
@@ -217,17 +226,22 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, action
 
 def textarea(label='', *, rows=6, code=None, maxlength=None, minlength=None, valid_func=None, name=None, value=None,
              placeholder=None, required=None, readonly=None, help_text=None, **other_html_attrs):
-    r"""文本输入域
+    r"""文本输入域(多行文本输入)
 
     :param int rows: 输入文本的行数(显示的高度)。输入的文本超出设定值时会显示滚动条
     :param int maxlength: 允许用户输入的最大字符长度 (Unicode) 。未指定表示无限长度
     :param int minlength: 允许用户输入的最小字符长度(Unicode)
-    :param dict code: 通过提供 `Codemirror <https://codemirror.net/>`_ 参数让文本输入域具有代码编辑器样式::
+    :param dict code: 通过提供 `Codemirror <https://codemirror.net/>`_ 参数让文本输入域具有代码编辑器样式:
+
+        .. exportable-codeblock::
+            :name: textarea-code
+            :summary: `textarea()`代码编辑
 
             res = textarea('Text area', code={
                 'mode': "python",
                 'theme': 'darcula'
             })
+            put_code(res, language='python')  # ..demo-only
 
         更多配置可以参考 https://codemirror.net/doc/manual.html#config
     :param - label, valid_func, name, value, placeholder, required, readonly, help_text, other_html_attrs: 与 `input` 输入函数的同名参数含义一致
@@ -272,7 +286,9 @@ def _set_options_selected(options, value):
 
 def select(label='', options=None, *, multiple=None, valid_func=None, name=None, value=None, required=None,
            help_text=None, **other_html_attrs):
-    r"""下拉选择框。默认单选,设置 multiple 参数后,可以多选。但都至少要选择一个选项。
+    r"""下拉选择框。
+
+    默认单选,设置 ``multiple`` 参数后,可以多选。但都至少要选择一个选项。
 
     :param list options: 可选项列表。列表项的可用形式有:
 
@@ -429,16 +445,27 @@ def actions(label='', buttons=None, name=None, help_text=None):
 
     .. _custom_form_ctrl_btn:
 
-    * 实现简单的选择操作::
+    * 实现简单的选择操作:
+
+    .. exportable-codeblock::
+        :name: actions-select
+        :summary: 使用`actions()`实现简单的选择操作
 
         confirm = actions('确认删除文件?', ['确认', '取消'], help_text='文件删除后不可恢复')
         if confirm=='确认':
-            ...
+            ...  # ..doc-only
+            put_text('已确认')  # ..demo-only
 
-      相比于其他输入项, `actions` 只需要用户点击一次就可完成提交。
+    相比于其他输入项,使用 `actions()` 用户只需要点击一次就可完成提交。
 
-    * 替换默认的提交按钮::
+    * 替换默认的提交按钮:
 
+    .. exportable-codeblock::
+        :name: actions-submit
+        :summary: 使用`actions()`替换默认的提交按钮
+
+        import json  # ..demo-only
+                     # ..demo-only
         info = input_group('Add user', [
             input('username', type=TEXT, name='username', required=True),
             input('password', type=PASSWORD, name='password', required=True),
@@ -449,12 +476,19 @@ def actions(label='', buttons=None, name=None, help_text=None):
                 {'label': '取消', 'type': 'cancel'},
             ], name='action', help_text='actions'),
         ])
+        put_code('info = ' + json.dumps(info, indent=4))
         if info is not None:
-            save_user(info['username'], info['password'])
+            save_user(info['username'], info['password'])  # ..doc-only
+            put_text(info['username'], info['password'])  # ..demo-only
             if info['action'] == 'save_and_continue':  # 选择了"保存并添加下一个"
-                add_next()
+                add_next()  # ..doc-only
+                ...  # ..demo-only
+
+    * 通过其他操作设置项值:
 
-    通过其他操作设置项值::
+    .. exportable-codeblock::
+        :name: actions-callback
+        :summary: `actions()`callback的使用
 
         def get_name(set_val):
             popup('Set name', [
@@ -578,18 +612,22 @@ def input_group(label='', inputs=None, valid_func=None, cancelable=False):
     :param list inputs: 输入项列表。列表的内容为对单项输入函数的调用,并在单项输入函数中传入 ``name`` 参数。
     :param Callable valid_func: 输入组校验函数。
         函数签名:``callback(data) -> (name, error_msg)``
-        ``valid_func`` 接收整个表单的值为参数,当校验表单值有效时,返回 ``None`` ,当某项输入值无效时,返回出错输入项的 ``name`` 值和错误提示. 比如::
-
-            def check_form(data):
-                if len(data['name']) > 6:
-                    return ('name', '名字太长!')
-                if data['age'] <= 0:
-                    return ('age', '年龄不能为负数!')
-
-            data = input_group("Basic info",[
-                input('Input your name', name='name'),
-                input('Repeat your age', name='age', type=NUMBER)
-            ], valid_func=check_form)
+        ``valid_func`` 接收整个表单的值为参数,当校验表单值有效时,返回 ``None`` ,当某项输入值无效时,返回出错输入项的 ``name`` 值和错误提示. 比如:
+
+    .. exportable-codeblock::
+        :name: input_group-valid_func
+        :summary: `input_group()`输入组校验
+
+        def check_form(data):
+            if len(data['name']) > 6:
+                return ('name', '名字太长!')
+            if data['age'] <= 0:
+                return ('age', '年龄不能为负数!')
+
+        data = input_group("Basic info",[
+            input('Input your name', name='name'),
+            input('Repeat your age', name='age', type=NUMBER)
+        ], valid_func=check_form)
 
         put_text(data['name'], data['age'])
 

+ 132 - 41
pywebio/output.py

@@ -2,6 +2,55 @@ r"""输出内容到用户浏览器
 
 本模块提供了一系列函数来输出不同形式的内容到用户浏览器,并支持灵活的输出控制。
 
+函数清单
+--------------
+
+* 输出域Scope
+
+    * `set_scope` : 创建一个新的scope.
+    * `get_scope` : 获取当前运行时scope栈中的scope名
+    * `clear` : 清空scope内容
+    * `remove` : 移除Scope
+    * `scroll_to` : 将页面滚动到 scope Scope处
+    * `use_scope` : 开启/进入输出域
+
+* 内容输出
+
+    * `put_text` : 输出文本
+    * `put_markdown` : 输出Markdown
+    * `put_html` : 输出Html
+    * `put_link` : 输出链接
+    * `put_processbar` : 输出进度条
+    * `set_processbar` : 设置进度条进度
+    * `put_loading` : 输出加载提示
+    * `put_code` : 输出代码块
+    * `put_table` : 输出表格
+    * `span` : 用于在 `put_table()` 和 `put_grid()` 中设置内容跨单元格
+    * `put_buttons` : 输出一组按钮,并绑定点击事件
+    * `put_image` : 输出图片
+    * `put_file` : 显示一个文件下载链接
+    * `put_collapse` : 输出可折叠的内容
+    * `put_scrollable` : 固定高度内容输出区域,内容超出则显示滚动条
+    * `put_widget` : 输出自定义的控件
+
+* 其他交互
+
+    * `toast` : 显示一条通知消息
+    * `popup` : 显示弹窗
+    * `close_popup` : 关闭正在显示的弹窗
+
+* 布局与样式
+
+    * `put_row` : 使用行布局输出内容
+    * `put_column` : 使用列布局输出内容
+    * `put_grid` : 使用网格布局输出内容
+    * `style` : 自定义输出内容的css样式
+
+* 其他
+
+    * `output` : 内容占位符
+
+
 输出域Scope
 --------------
 .. autofunction:: set_scope
@@ -334,7 +383,9 @@ def span(content, row=1, col=1):
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: span
+        :summary: 使用`span()`合并单元格
 
         put_table([
             ['C'],
@@ -365,7 +416,11 @@ def put_table(tdata, header=None, scope=Scope.Current, position=OutputPosition.B
 
     :param int scope, position: 与 `put_text` 函数的同名参数含义一致
 
-    使用示例::
+    使用示例:
+
+    .. exportable-codeblock::
+        :name: put_table
+        :summary: 使用`put_table()`输出表格
 
         # 'Name'单元格跨2行、'Address'单元格跨2列
         put_table([
@@ -374,23 +429,27 @@ def put_table(tdata, header=None, scope=Scope.Current, position=OutputPosition.B
             ['Wang', 'Beijing', 'China'],
             ['Liu', 'New York', 'America'],
         ])
+        ## ----
 
         # 单元格为 ``put_xxx`` 类型的输出函数
         put_table([
             ['Type', 'Content'],
             ['html', put_html('X<sup>2</sup>')],
             ['text', '<hr/>'],
-            ['buttons', put_buttons(['A', 'B'], onclick=...)],
+            ['buttons', put_buttons(['A', 'B'], onclick=...)],  # ..doc-only
+            ['buttons', put_buttons(['A', 'B'], onclick=ut_text)],  # ..demo-only
             ['markdown', put_markdown('`Awesome PyWebIO!`')],
             ['file', put_file('hello.text', b'')],
             ['table', put_table([['A', 'B'], ['C', 'D']])]
         ])
+        ## ----
 
         # 设置表头
         put_table([
             ['Wang', 'M', 'China'],
             ['Liu', 'W', 'America'],
         ], header=['Name', 'Gender', 'Address'])
+        ## ----
 
         # dict类型的表格行
         put_table([
@@ -465,22 +524,6 @@ def table_cell_buttons(buttons, onclick, **callback_options) -> Output:
 
     :param str buttons, onclick, save: 与 `put_buttons` 函数的同名参数含义一致
 
-    .. _table_cell_buttons-code-sample:
-
-    使用示例::
-
-        from functools import partial
-
-        def edit_row(choice, row):
-            put_text("You click %s button at row %s" % (choice, row))
-
-        put_table([
-            ['Idx', 'Actions'],
-            ['1', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
-            ['2', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
-            ['3', table_cell_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
-        ])
-
     .. deprecated:: 0.3
        Use :func:`put_buttons()` instead
     """
@@ -492,7 +535,7 @@ def table_cell_buttons(buttons, onclick, **callback_options) -> Output:
 def put_buttons(buttons, onclick, small=None, link_style=False, scope=Scope.Current, position=OutputPosition.BOTTOM,
                 **callback_options) -> Output:
     """
-    输出一组按钮
+    输出一组按钮,并绑定点击事件
 
     :param list buttons: 按钮列表。列表项的可用形式有:
 
@@ -523,7 +566,11 @@ def put_buttons(buttons, onclick, small=None, link_style=False, scope=Scope.Curr
            对于开启了serial_mode的回调,都会在会话内的一个固定线程内执行,当会话运行此回调时,其他所有新的点击事件的回调(包括 ``serial_mode=False`` 的回调)都将排队等待当前点击事件运行完成。
            如果回调函数运行时间很短,可以开启 ``serial_mode`` 来提高性能。
 
-    使用示例::
+    使用示例:
+
+    .. exportable-codeblock::
+        :name: put_buttons
+        :summary: 使用`put_buttons()`输出按钮
 
         from functools import partial
 
@@ -531,11 +578,13 @@ def put_buttons(buttons, onclick, small=None, link_style=False, scope=Scope.Curr
             put_text("You click %s button with id: %s" % (choice, id))
 
         put_buttons(['edit', 'delete'], onclick=partial(row_action, id=1))
+        ## ----
 
         def edit():
-            ...
+            put_text("You click edit button")
         def delete():
-            ...
+            put_text("You click delete button")
+
         put_buttons(['edit', 'delete'], onclick=[edit, delete])
     """
     btns = _format_button(buttons)
@@ -677,8 +726,13 @@ def put_loading(shape='border', color='dark', scope=Scope.Current, position=Outp
     :param int scope, position: 与 `put_text` 函数的同名参数含义一致
 
     .. note::
-        可以通过 :func:`style()` 设置加载提示的尺寸::
+        可以通过 :func:`style()` 设置加载提示的尺寸:
+
+        .. exportable-codeblock::
+            :name: put_loading-size
+            :summary: `put_loading()`自定义加载提示尺寸
 
+            put_loading()  # ..demo-only
             style(put_loading(), 'width:4rem; height:4rem')
     """
     assert shape in ('border', 'grow'), "shape must in ('border', 'grow')"
@@ -718,7 +772,7 @@ def put_collapse(title, content, open=False, scope=Scope.Current, position=Outpu
 @safely_destruct_output_when_exp('content')
 def put_scrollable(content, max_height=400, horizon_scroll=False, border=True, scope=Scope.Current,
                    position=OutputPosition.BOTTOM) -> Output:
-    """宽高限制的内容输出区域,内容超出限制则显示滚动条
+    """固定高度内容输出区域,内容超出则显示滚动条
 
     :type content: list/str/put_xxx()
     :param content: 内容可以为字符串或 ``put_xxx`` 类输出函数的返回值,或者由它们组成的列表。
@@ -766,7 +820,9 @@ def put_widget(template, data, scope=Scope.Current, position=OutputPosition.BOTT
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: put_widget
+        :summary: 使用`put_widget()`输出自定义的控件
 
         tpl = '''
         <details>
@@ -817,11 +873,14 @@ def put_row(content, size=None, scope=Scope.Current, position=OutputPosition.BOT
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: put_row
+        :summary: 使用`put_row()`进行行布局
 
         put_row([put_code('A'), None, put_code('B')])  # 左右两个等宽度的代码块,中间间隔10像素
+        ## ----
 
-        put_row([put_image(...), put_image(...)], '40% 60%')  # 左右两图宽度比2:3, 和size='2fr 3fr'等价
+        put_row([put_code('A'), None, put_code('B')], size='40% 10px 60%')  # 左右两代码块宽度比2:3, 和size='2fr 10px 3fr'等价
 
     """
     return _row_column_layout(content, flow='column', size=size, scope=scope, position=position)
@@ -880,7 +939,9 @@ def put_grid(content, cell_width='auto', cell_height='auto', cell_widths=None, c
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: put_grid
+        :summary: 使用`put_grid()`进行网格布局
 
         put_grid([
             [put_text('A'), put_text('B'), put_text('C')],
@@ -959,17 +1020,22 @@ def output(*contents):
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: output
+        :summary: 内容占位符——`output()`
 
         hobby = output(put_text('Coding'))
         put_table([
-            ['Name', 'Hobbies'],
-            ['Wang', hobby]
+           ['Name', 'Hobbies'],
+           ['Wang', hobby]      # hobby 初始为 Coding
         ])
+        ## ----
 
-        hobby.reset(put_text('Movie'))
-        hobby.append(put_text('Music'), put_text('Drama'))
-        hobby.insert(0, put_markdown('**Coding**'))
+        hobby.reset(put_text('Movie'))  # hobby 被重置为 Movie
+        ## ----
+        hobby.append(put_text('Music'), put_text('Drama'))   # 向 hobby 追加 Music, Drama
+        ## ----
+        hobby.insert(0, put_markdown('**Coding**'))  # 将 Coding 插入 hobby 顶端
 
     """
 
@@ -1031,20 +1097,25 @@ def style(outputs, css_style) -> Union[Output, OutputList]:
 
     :Example:
 
-    ::
+    .. exportable-codeblock::
+        :name: style
+        :summary: 使用`style()`自定义内容样式
 
         style(put_text('Red'), 'color:red')
 
+        ## ----
         style([
             put_text('Red'),
             put_markdown('~~del~~')
         ], 'color:red')
 
+        ## ----
         put_table([
             ['A', 'B'],
             ['C', style(put_text('Red'), 'color:red')],
         ])
 
+        ## ----
         put_collapse('title', style([
             put_text('text'),
             put_markdown('~~del~~'),
@@ -1088,9 +1159,14 @@ def popup(title, content=None, size=PopupSize.NORMAL, implicit_close=True, closa
 
     支持直接传入内容、上下文管理器、装饰器三种形式的调用
 
-    * 直接传入内容::
+    * 直接传入内容:
+
+    .. exportable-codeblock::
+        :name: popup
+        :summary: 直接调用`popup()`来显示弹窗
 
         popup('popup title', 'popup text content', size=PopupSize.SMALL)
+        ## ----
 
         popup('Popup title', [
             put_html('<h3>Popup Content</h3>'),
@@ -1099,7 +1175,11 @@ def popup(title, content=None, size=PopupSize.NORMAL, implicit_close=True, closa
             put_buttons(['close_popup()'], onclick=lambda _: close_popup())
         ])
 
-    * 作为上下文管理器使用::
+    * 作为上下文管理器使用:
+
+    .. exportable-codeblock::
+        :name: popup-context
+        :summary: 将`popup()`作为下文管理器来创建弹窗
 
         with popup('Popup title') as s:
             put_html('<h3>Popup Content</h3>')
@@ -1112,11 +1192,16 @@ def popup(title, content=None, size=PopupSize.NORMAL, implicit_close=True, closa
     上下文管理器会开启一个新的输出域并返回Scope名,上下文管理器中的输出调用会显示到弹窗上。
     上下文管理器退出后,弹窗并不会关闭,依然可以使用 ``scope`` 参数输出内容到弹窗。
 
-    * 作为装饰器使用::
+    * 作为装饰器使用:
+
+    .. exportable-codeblock::
+        :name: popup-context
+        :summary: 将`popup()`作为装饰器使用
 
         @popup('Popup title')
         def show_popup():
-            put_xxx()
+            put_html('<h3>Popup Content</h3>')
+            put_text("I'm in a popup!")
             ...
 
         show_popup()
@@ -1156,7 +1241,11 @@ def toast(content, duration=2, position='center', color='info', onclick=None):
 
         Note: 当使用 :ref:`基于协程的会话实现 <coroutine_based_session>` 时,回调函数可以为协程函数.
 
-    Example::
+    Example:
+
+    .. exportable-codeblock::
+        :name: toast
+        :summary: 使用`toast()`显示通知
 
         def show_msg():
             put_text("Some messages...")
@@ -1184,6 +1273,8 @@ clear_scope = clear
 def use_scope(name=None, clear=False, create_scope=True, **scope_params):
     """scope的上下文管理器和装饰器
 
+    参见 :ref:`用户手册-use_scope() <use_scope>`
+
     :param name: scope名. 若为None则生成一个全局唯一的scope名.(以上下文管理器形式的调用时,上下文管理器会返回scope名)
     :param bool clear: 是否要清除scope内容
     :param bool create_scope: scope不存在时是否创建scope