暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Django:集成富文本编辑器「CKEditor5」

Nephilim 2024-04-30
662

Tips:一些记录,一些笔记



2024/04/28

SUNDAY

The only limit to our realization of tomorrow will be our doubts of today.

实现明天理想的唯一障碍是今天的疑虑。




01

CKEditor5


官方网站:

https://ckeditor.com/



CKEditor 5:所有特性:
https://ckeditor.com/docs/ckeditor5/latest/features/index.html


如果希望在Django中集成CKEditor,有两种方式:

  • 通过官方网站下载CKEditor的安装介质,然后进行一步一步的配置

  • 通过Django的Python软件包安装后,启用插件后,进行简单的配置


如果选择第一种方式,你首先需要在下面的官方下载页面获取你需要的软件包:

https://ckeditor.com/ckeditor-5/download/

https://ckeditor.com/ckeditor-5/online-builder/


另一种方式,你需要通过「PIP」安装相关的Python软件:

  • django-ckeditor

  • django-ckeditor5


如果你在百度或者谷歌上搜索「Django集成CKEditor」你找到的大部分的文档都是通过PIP安装,然后进行简单配置实现的。


在这里,以上两种方式我都会进行说明。


02

「方式一」CKEditor:定制自己想要的插件(Plugin)


可以在官方网站定制自己想要的插件(Plugin)

选好后,可以在官方网站在线打包:


然后,将最终生成的压缩包下载下来:


03

在Django中引入「CKEditor」


然后,解压:

解压后,可以先在「sample」中看看最终生成的效果是不是你期望的「编辑器的样子」

打开「index.html」


将解压的程序放入Django的「static」中:


我们需要放到Django中的部分是「build」文件夹中的这部分文件:

然后,就可以进行使用了。


04

在Django中使用CKEditor「前台页面展示」


在Django中使用CKEditor分为两个场景:

  • Django后台的Admin管理页面

  • Django前台的页面展示


一、「Django前台」的页面展示


先看效果:


这里,以「wiki_add.html」为例,所有的CKEditor的使用、JS的定义、CSS的配置都会围绕这个页面展开,这样也便于理解在前端页面中,到底是如何使用CKEditor的。


CSS样式表:

文件「ckeditor.css」

    /**
    * @license Copyright (c) 2014-2024, CKSource Holding sp. z o.o. All rights reserved.
    * This file is licensed under the terms of the MIT License (see LICENSE.md).
    */


    :root {
    --ck-sample-base-spacing: 2em;
    --ck-sample-color-white: #fff;
    --ck-sample-color-green: #279863;
    --ck-sample-color-blue: #1a9aef;
    --ck-sample-container-width: 1285px;
    --ck-sample-sidebar-width: 350px;
    --ck-sample-editor-min-height: 400px;
    --ck-sample-editor-z-index: 10;
    }


    /* --------- EDITOR STYLES ---------------------------------------------------------------------------------------- */


    .editor__editable,
    /* Classic build. */
    main .ck-editor[role='application'] .ck.ck-content,
    /* Decoupled document build. */
    .ck.editor__editable[role='textbox'],
    .ck.ck-editor__editable[role='textbox'],
    /* Inline & Balloon build. */
    .ck.editor[role='textbox'] {
    width: 100%;
    background: #fff;
    font-size: 1em;
    line-height: 1.6em;
    min-height: var(--ck-sample-editor-min-height);
    padding: 1.5em 2em;
    }


    .ck.ck-editor__editable {
    background: #fff;
    border: 1px solid hsl(0, 0%, 70%);
    width: 100%;
    }


    /* Because of sidebar `position: relative`, Edge is overriding the outline of a focused editor. */
    .ck.ck-editor__editable {
    position: relative;
    z-index: var(--ck-sample-editor-z-index);
    }


    .editor-container {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    position: relative;
    width: 100%;
    height: 100%;
    justify-content: center;
    margin: 20px auto 20px auto;
    }


    .editor-container--with-sidebar > .ck.ck-editor {
    width: calc( 100% - var(--ck-sample-sidebar-width) );
    }


    /* --------- DECOUPLED (DOCUMENT) BUILD. ---------------------------------------------*/
    body[data-editor='DecoupledEditor'] .document-editor__toolbar {
    width: 100%;
    }


    body[data-editor='DecoupledEditor'] .collaboration-demo__editable,
    body[data-editor='DecoupledEditor'] .row-editor .editor {
    /* A pixel is added for each of the border. */
    width: calc(21cm + 2px);
    min-height: calc(29.7cm + 2px);
    /* To avoid having extra scrolls inside the editor container. */
    height: fit-content;
    padding: 2cm 1.2cm;
    margin: 2.5rem;
    border: 1px hsl( 0, 0%, 82.7% ) solid;
    background-color: var(--ck-sample-color-white);
    box-shadow: 0 0 5px hsla( 0, 0%, 0%, .1 );
    box-sizing: border-box;
    }


    body[data-editor='DecoupledEditor'] .row-editor {
    display: flex;
    position: relative;
    justify-content: center;
    overflow-y: auto;
    background-color: #f2f2f2;
    border: 1px solid hsl(0, 0%, 77%);
    /* Limit the max-height of the editor to avoid scrolling from bottom to top to see the toolbar. */
    max-height: 700px;
    }


    body[data-editor='DecoupledEditor'] .sidebar {
    background: transparent;
    border: 0;
    box-shadow: none;
    }


    /* --------- COMMENTS & TRACK CHANGES FEATURE ---------------------------------------------------------------------- */
    .sidebar {
    padding: 0 15px;
    position: relative;
    min-width: var(--ck-sample-sidebar-width);
    max-width: var(--ck-sample-sidebar-width);
    font-size: 20px;
    border: 1px solid hsl(0, 0%, 77%);
    background: hsl(0, 0%, 98%);
    border-left: 0;
    overflow: hidden;
    min-height: 100%;
    flex-grow: 1;
    }


    /* Do not inherit styles related to the editable editor content. See line 25.*/
    .sidebar .ck-content[role='textbox'],
    .ck.ck-annotation-wrapper .ck-content[role='textbox'] {
    min-height: unset;
    width: unset;
    padding: 0;
    background: transparent;
    }


    .sidebar.narrow {
    min-width: 60px;
    flex-grow: 0;
    }


    .sidebar.hidden {
    display: none !important;
    }


    #sidebar-display-toggle {
    position: absolute;
    z-index: 1;
    width: 30px;
    height: 30px;
    text-align: center;
    left: 15px;
    top: 30px;
    border: 0;
    padding: 0;
    color: hsl( 0, 0%, 50% );
    transition: 250ms ease color;
    background-color: transparent;
    }


    #sidebar-display-toggle:hover {
    color: hsl( 0, 0%, 30% );
    cursor: pointer;
    }


    #sidebar-display-toggle:focus,
    #sidebar-display-toggle:active {
    outline: none;
    border: 1px solid #a9d29d;
    }


    #sidebar-display-toggle svg {
    fill: currentColor;
    }


    /* --------- COLLABORATION FEATURES (USERS) ------------------------------------------------------------------------ */
    .row-presence {
    width: 100%;
    border: 1px solid hsl(0, 0%, 77%);
    border-bottom: 0;
    background: hsl(0, 0%, 98%);
    padding: var(--ck-spacing-small);


    /* Make `border-bottom` as `box-shadow` to not overlap with the editor border. */
    box-shadow: 0 1px 0 0 hsl(0, 0%, 77%);


    /* Make `z-index` bigger than `.editor` to properly display tooltips. */
    z-index: 20;
    }


    .ck.ck-presence-list {
    flex: 1;
    padding: 1.25rem .75rem;
    }


    .presence .ck.ck-presence-list__counter {
    order: 2;
    margin-left: var(--ck-spacing-large)
    }


    /* --------- REAL TIME COLLABORATION FEATURES (SHARE TOPBAR CONTAINER) --------------------------------------------- */
    .collaboration-demo__row {
    display: flex;
    position: relative;
    justify-content: center;
    overflow-y: auto;
    background-color: #f2f2f2;
    border: 1px solid hsl(0, 0%, 77%);
    }


    body[data-editor='InlineEditor'] .collaboration-demo__row {
    border: 0;
    }


    .collaboration-demo__container {
    max-width: var(--ck-sample-container-width);
    margin: 0 auto;
    padding: 1.25rem;
    }


    .presence, .collaboration-demo__row {
    transition: .2s opacity;
    }


    .collaboration-demo__topbar {
    background: #fff;
    border: 1px solid var(--ck-color-toolbar-border);
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 0;
    border-radius: 4px 4px 0 0;
    }


    .collaboration-demo__topbar .btn {
    margin-right: 1em;
    outline-offset: 2px;
    outline-width: 2px;
    background-color: var( --ck-sample-color-blue );
    }


    .collaboration-demo__topbar .btn:focus,
    .collaboration-demo__topbar .btn:hover {
    border-color: var( --ck-sample-color-blue );
    }


    .collaboration-demo__share {
    display: flex;
    align-items: center;
    padding: 1.25rem .75rem
    }


    .collaboration-demo__share-description p {
    margin: 0;
    font-weight: bold;
    font-size: 0.9em;
    }


    .collaboration-demo__share input {
    height: auto;
    font-size: 0.9em;
    min-width: 220px;
    margin: 0 10px;
    border-radius: 4px;
    border: 1px solid var(--ck-color-toolbar-border)
    }


    .collaboration-demo__share button,
    .collaboration-demo__share input {
    height: 40px;
    padding: 5px 10px;
    }


    .collaboration-demo__share button {
    position: relative;
    }


    .collaboration-demo__share button:focus {
    outline: none;
    }


    .collaboration-demo__share button[data-tooltip]::before,
    .collaboration-demo__share button[data-tooltip]::after {
    position: absolute;
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
    transition: all .15s cubic-bezier(.5,1,.25,1);
    z-index: 1;
    }


    .collaboration-demo__share button[data-tooltip]::before {
    content: attr(data-tooltip);
    padding: 5px 15px;
    border-radius: 3px;
    background: #111;
    color: #fff;
    text-align: center;
    font-size: 11px;
    top: 100%;
    left: 50%;
    margin-top: 5px;
    transform: translateX(-50%);
    }


    .collaboration-demo__share button[data-tooltip]::after {
    content: '';
    border: 5px solid transparent;
    width: 0;
    font-size: 0;
    line-height: 0;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border-bottom: 5px solid #111;
    border-top: none;
    }


    .collaboration-demo__share button[data-tooltip]:hover:before,
    .collaboration-demo__share button[data-tooltip]:hover:after {
    visibility: visible;
    opacity: 1;
    }


    .collaboration-demo--ready {
    overflow: visible;
    height: auto;
    }


    .collaboration-demo--ready .presence,
    .collaboration-demo--ready .collaboration-demo__row {
    opacity: 1;
    }


    /* --------- PAGINATION FEATURE ------------------------------------------------------------------------------------ */


    /* Pagination view line must be stacked at least at the same level as the editor,
    otherwise it will be hidden underneath. */
    .ck.ck-pagination-view-line {
    z-index: var(--ck-sample-editor-z-index);
    }


    /* --------- REVISION HISTORY FEATURE ------------------------------------------------------------------------------ */


    .revision-viewer-container {
    display: none;
    max-width: 100%;
    word-wrap: break-word;
    }


    .revision-viewer-sidebar {
    position: relative;
    min-width: 310px;
    overflow: hidden;
    background: var(--ck-color-toolbar-background);
    border: 1px solid var(--ck-color-toolbar-border);
    margin-left: -1px;
    }


    /* A case when Pagination and Revision History features are enabled in the editor. */
    /* Move the square with page number from the Pagination plugin to the left side, so that it does not cover the RH sidebar. */
    body[data-revision-history='true'] .ck.ck-pagination-view-line::after {
    transform: translateX(-100%) !important;
    left: -1px !important;
    right: unset !important;
    }


    /* --------- DOCUMENT OUTLINE FEATURE ------------------------------------------------------------------------------ */


    .document-outline-container {
    max-height: 80vh;
    overflow-y: auto;
    margin-bottom: 1em;
    }


    .document-outline-container .ck.ck-document-outline {
    min-height: 100%;
    border: 1px solid var(--ck-color-base-border);
    background-color: hsl(0, 0%, 96%);
    }


    /* --------- SAMPLE GENERIC STYLES (not related to CKEditor) ------------------------------------------------------- */
    body, html {
    padding: 0;
    margin: 0;


    font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 16px;
    line-height: 1.5;
    }


    body {
    height: 100%;
    color: #2D3A4A;
    }


    body * {
    box-sizing: border-box;
    }


    a {
    color: #38A5EE;
    }


    header .centered {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-between;
    align-items: center;
    min-height: 8em;
    }


    header h1 a {
    font-size: 20px;
    display: flex;
    align-items: center;
    color: #2D3A4A;
    text-decoration: none;
    }


    header h1 img {
    display: block;
    height: 64px;
    }


    header nav ul {
    margin: 0;
    padding: 0;
    list-style-type: none;
    }


    header nav ul li {
    display: inline-block;
    }


    header nav ul li + li {
    margin-left: 1em;
    }


    header nav ul li a {
    font-weight: bold;
    text-decoration: none;
    color: #2D3A4A;
    }


    header nav ul li a:hover {
    text-decoration: underline;
    }


    main .message {
    padding: 0 0 var(--ck-sample-base-spacing);
    background: var(--ck-sample-color-green);
    color: var(--ck-sample-color-white);
    }


    main .message::after {
    content: "";
    z-index: -1;
    display: block;
    height: 10em;
    width: 100%;
    background: var(--ck-sample-color-green);
    position: absolute;
    left: 0;
    }


    main .message h2 {
    position: relative;
    padding-top: 1em;
    font-size: 2em;
    }


    .centered {
    max-width: var(--ck-sample-container-width);
    margin: 0 auto;
    padding: 0 var(--ck-sample-base-spacing);
    }


    .row {
    display: flex;
    position: relative;
    }


    .btn {
    cursor: pointer;
    padding: 8px 16px;
    font-size: 1rem;
    user-select: none;
    border-radius: 4px;
    transition: color .2s ease-in-out,background-color .2s ease-in-out,border-color .2s ease-in-out,opacity .2s ease-in-out;
    background-color: var(--ck-sample-color-button-blue);
    border-color: var(--ck-sample-color-button-blue);
    color: var(--ck-sample-color-white);
    display: inline-block;
    }


    .btn--tiny {
    padding: 6px 12px;
    font-size: .8rem;
    }


    footer {
    margin: calc(2*var(--ck-sample-base-spacing)) var(--ck-sample-base-spacing);
    font-size: .8em;
    text-align: center;
    color: rgba(0,0,0,.4);
    }


    /* --------- RWD --------------------------------------------------------------------------------------------------- */
    @media screen and ( max-width: 800px ) {
    :root {
    --ck-sample-base-spacing: 1em;
    }


    header h1 {
    width: 100%;
    }


    header h1 img {
    height: 40px;
    }


    header nav ul {
    text-align: right;
    }


    main .message h2 {
    font-size: 1.5em;
    }
    }


    JS的定义:

    文件「config.project.wiki.add.js」

      DecoupledEditor
      .create( document.querySelector( '.editor' ), {
      // Editor configuration.
      } )
      .then( editor => {
      window.editor = editor;


      // Set a custom container for the toolbar.
      document.querySelector( '.document-editor__toolbar' ).appendChild( editor.ui.view.toolbar.element );
      document.querySelector( '.ck-toolbar' ).classList.add( 'ck-reset_all' );
      } )
      .catch( handleSampleError );


      function handleSampleError( error ) {
      const issueUrl = 'https://github.com/ckeditor/ckeditor5/issues';


      const message = [
      'Oops, something went wrong!',
      `Please, report the following error on ${ issueUrl } with the build id "sv0v4xk1a70f-rfval9obuxru" and the error stack trace:`
      ].join( '\n' );


      console.error( message );
      console.error( error );
      }



      页面设置:

      母页(基础页)「project_manage.html」

      在它的「body」上需要设置针对CKEditor5的属性:

        <body data-editor="DecoupledEditor" data-collaboration="false" data-revision-history="false">


        具体的插入富文本编辑器的页面「wiki_add.html」


        继承母页的配置:

        引入前面的CSS文件:

        然后,在内容(content block)区域定义需要渲染CKEditor的位置:

        最后,在「script」区域引用相关的JS:


        该页面,完整的文件内容如下:

          {% extends 'layout/project_manage.html' %}
          {% load static %}
          {% block css %}
          <style>
          .bd-placeholder-img {
          font-size: 1.125rem;
          text-anchor: middle;
          -webkit-user-select: none;
          -moz-user-select: none;
          user-select: none;
          }


          @media (min-width: 768px) {
          .bd-placeholder-img-lg {
          font-size: 3.5rem;
          }
          }


          .b-example-divider {
          height: 3rem;
          background-color: rgba(0, 0, 0, .1);
          border: solid rgba(0, 0, 0, .15);
          border-width: 1px 0;
          box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
          }


          .b-example-vr {
          flex-shrink: 0;
          width: 0.5rem;
          height: 100vh;
          }


          .bi {
          vertical-align: -.125em;
          fill: currentColor;
          }


          .nav-scroller {
          position: relative;
          z-index: 2;
          height: 2.75rem;
          overflow-y: hidden;
          }


          .nav-scroller .nav {
          display: flex;
          flex-wrap: nowrap;
          padding-bottom: 1rem;
          margin-top: -1px;
          overflow-x: auto;
          text-align: center;
          white-space: nowrap;
          -webkit-overflow-scrolling: touch;
          }


          .FormFolderCreateDetail {
          display: flex;
          flex-direction: column;
          background: rgba(31, 34, 37, 0.03);
          box-shadow: rgba(31, 34, 37, 0.06) 0px 0px 0px 1px inset;
          padding: 4px;
          gap: 4px;
          margin: 12px 0px;
          border-radius: 6px;
          }


          .FormFolderCreateDetailButton {
          font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
          -webkit-font-smoothing: antialiased;
          font-variant-numeric: lining-nums;
          font-feature-settings: "lnum", "calt";
          font-size: 13px;
          font-weight: 500;
          font-style: normal;
          font-stretch: normal;
          letter-spacing: -0.04px;
          line-height: 16px;
          display: grid;
          grid-template-columns: 1fr;
          -webkit-box-pack: justify;
          justify-content: space-between;
          -webkit-box-align: center;
          align-items: center;
          width: 100%;
          height: 32px;
          user-select: none;
          appearance: none;
          box-sizing: border-box;
          text-decoration: none;
          padding: 6px 8px;
          gap: 8px;
          border-radius: 6px;
          transition: background 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s, color, opacity;
          }


          .FormFolderCreateDetailButton:not(:disabled):not(.isStatic) {
          cursor: pointer;
          -webkit-tap-highlight-color: transparent;
          }


          .FormFolderCreateDetailButton.AppearanceDefault {
          background: rgba(31, 34, 37, 0);
          color: rgba(31, 34, 37, 0.9);
          }


          .FormFolderCreateDetailButton.hasRightContent {
          grid-template-columns: 1fr auto;
          }


          .FormFolderCreateDetailSpan {
          display: flex;
          flex-direction: column;
          padding: 2px 0px;
          text-align: left;
          min-width: 0px;
          }
          </style>
          <link rel="stylesheet" href="{% static 'css/project/manage/sidebars.css' %}">
          <link rel="stylesheet" href="{% static 'css/ckeditor/ckeditor.css' %}">


          <!--
          <link rel="stylesheet" href="{% static 'plugin/wangeditor/css/style.css' %}">
          -->
          {% endblock %}


          {% block content %}


          <!-- SVG -->
          <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
          <symbol id="bootstrap" viewBox="0 0 118 94">
          <title>Bootstrap</title>
          <path fill-rule="evenodd" clip-rule="evenodd" d="M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z"></path>
          </symbol>
          <symbol id="home" viewBox="0 0 16 16">
          <path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z"/>
          </symbol>
          <symbol id="speedometer2" viewBox="0 0 16 16">
          <path d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"/>
          <path fill-rule="evenodd" d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z"/>
          </symbol>
          <symbol id="table" viewBox="0 0 16 16">
          <path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/>
          </symbol>
          <symbol id="people-circle" viewBox="0 0 16 16">
          <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
          <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
          </symbol>
          <symbol id="grid" viewBox="0 0 16 16">
          <path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"/>
          </symbol>
          <symbol id="collection" viewBox="0 0 16 16">
          <path d="M2.5 3.5a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-11zm2-2a.5.5 0 0 1 0-1h7a.5.5 0 0 1 0 1h-7zM0 13a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 16 13V6a1.5 1.5 0 0 0-1.5-1.5h-13A1.5 1.5 0 0 0 0 6v7zm1.5.5A.5.5 0 0 1 1 13V6a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-13z"/>
          </symbol>
          <symbol id="calendar3" viewBox="0 0 16 16">
          <path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"/>
          <path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
          </symbol>
          <symbol id="chat-quote-fill" viewBox="0 0 16 16">
          <path d="M16 8c0 3.866-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7zM7.194 6.766a1.688 1.688 0 0 0-.227-.272 1.467 1.467 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 5.734 6C4.776 6 4 6.746 4 7.667c0 .92.776 1.666 1.734 1.666.343 0 .662-.095.931-.26-.137.389-.39.804-.81 1.22a.405.405 0 0 0 .011.59c.173.16.447.155.614-.01 1.334-1.329 1.37-2.758.941-3.706a2.461 2.461 0 0 0-.227-.4zM11 9.073c-.136.389-.39.804-.81 1.22a.405.405 0 0 0 .012.59c.172.16.446.155.613-.01 1.334-1.329 1.37-2.758.942-3.706a2.466 2.466 0 0 0-.228-.4 1.686 1.686 0 0 0-.227-.273 1.466 1.466 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 10.07 6c-.957 0-1.734.746-1.734 1.667 0 .92.777 1.666 1.734 1.666.343 0 .662-.095.931-.26z"/>
          </symbol>
          <symbol id="cpu-fill" viewBox="0 0 16 16">
          <path d="M6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"/>
          <path d="M5.5.5a.5.5 0 0 0-1 0V2A2.5 2.5 0 0 0 2 4.5H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2A2.5 2.5 0 0 0 4.5 14v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14a2.5 2.5 0 0 0 2.5-2.5h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14A2.5 2.5 0 0 0 11.5 2V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5zm1 4.5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3A1.5 1.5 0 0 1 6.5 5z"/>
          </symbol>
          <symbol id="gear-fill" viewBox="0 0 16 16">
          <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
          </symbol>
          <symbol id="speedometer" viewBox="0 0 16 16">
          <path d="M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2zM3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.389.389 0 0 0-.029-.518z"/>
          <path fill-rule="evenodd" d="M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.945 11.945 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0z"/>
          </symbol>
          <symbol id="toggles2" viewBox="0 0 16 16">
          <path d="M9.465 10H12a2 2 0 1 1 0 4H9.465c.34-.588.535-1.271.535-2 0-.729-.195-1.412-.535-2z"/>
          <path d="M6 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 1a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm.535-10a3.975 3.975 0 0 1-.409-1H4a1 1 0 0 1 0-2h2.126c.091-.355.23-.69.41-1H4a2 2 0 1 0 0 4h2.535z"/>
          <path d="M14 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0z"/>
          </symbol>
          <symbol id="tools" viewBox="0 0 16 16">
          <path d="M1 0L0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.356 3.356a1 1 0 0 0 1.414 0l1.586-1.586a1 1 0 0 0 0-1.414l-3.356-3.356a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3c0-.269-.035-.53-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814L1 0zm9.646 10.646a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708zM3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026L3 11z"/>
          </symbol>
          <symbol id="chevron-right" viewBox="0 0 16 16">
          <path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
          </symbol>
          <symbol id="geo-fill" viewBox="0 0 16 16">
          <path fill-rule="evenodd" d="M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411z"/>
          </symbol>
          </svg>


          <main class="d-flex flex-nowrap">
          <!-- 左侧 SideBars -->
          <div class="d-flex flex-column flex-shrink-0 p-3 bg-white" style="width: 280px;">


          <!-- 头部图标 -->


          <a href="#" class="d-flex align-items-center link-dark text-decoration-none pb-1 mb-1">
          <svg t="1714013295044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1324" width="24" height="24"><path d="M512 512m-485.4784 0a485.4784 485.4784 0 1 0 970.9568 0 485.4784 485.4784 0 1 0-970.9568 0Z" fill="#A4E5FF" p-id="1325"></path><path d="M240.64 357.4272v124.6208h244.9408V258.3552H339.712A99.072 99.072 0 0 0 240.64 357.4272zM684.288 258.3552h-145.8688v223.6928H783.36V357.4272a99.072 99.072 0 0 0-99.072-99.072zM240.64 666.5728a99.072 99.072 0 0 0 99.072 99.072h145.8688v-229.0176H240.64zM538.4192 765.6448h145.8688A99.072 99.072 0 0 0 783.36 666.5728v-129.9456h-244.9408z" fill="#FFFFFF" p-id="1326"></path></svg>
          <span style="margin-left: 10px;">All Documents</span>
          </a>


          <a href="#" class="d-flex align-items-center link-dark text-decoration-none pb-1 mb-1">
          <svg t="1714013254347" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1172" width="24" height="24"><path d="M512 512m-485.4784 0a485.4784 485.4784 0 1 0 970.9568 0 485.4784 485.4784 0 1 0-970.9568 0Z" fill="#FFDC78" p-id="1173"></path><path d="M512 232.8576A279.1424 279.1424 0 1 0 791.1424 512 279.1424 279.1424 0 0 0 512 232.8576z m128.3584 367.872a25.6 25.6 0 0 1-35.5328 6.8608L496.64 534.6816a25.6 25.6 0 0 1-11.264-21.1968V388.6592a25.6 25.6 0 0 1 51.2 0v111.2064l96.768 65.28a25.6 25.6 0 0 1 7.0144 35.584z" fill="#FFFFFF" p-id="1174"></path></svg>
          <span style="margin-left: 10px;">Recents</span>
          </a>


          <a href="#" class="d-flex align-items-center link-dark text-decoration-none pb-2 mb-2 border-bottom">
          <svg t="1714013232606" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1020" width="24" height="24"><path d="M512 512m-485.4784 0a485.4784 485.4784 0 1 0 970.9568 0 485.4784 485.4784 0 1 0-970.9568 0Z" fill="#BEA3FB" p-id="1021"></path><path d="M539.0848 270.08l68.1984 138.24 152.4736 22.1184a30.208 30.208 0 0 1 16.7424 51.5072l-110.336 107.52 26.0608 151.9104a30.208 30.208 0 0 1-43.8272 31.8464L512 701.44l-136.3968 71.68a30.208 30.208 0 0 1-43.8272-31.8464l26.0608-151.9104-110.336-107.52a30.208 30.208 0 0 1 16.7424-51.5072l152.4736-22.1184 68.1984-138.24a30.208 30.208 0 0 1 54.1696 0.1024z" fill="#FFFFFF" p-id="1022"></path></svg>
          <span style="margin-left: 10px;">Starred</span>
          </a>


          <!-- 头部以下的「树形菜单」 -->
          <ul class="list-unstyled ps-0">


          <!-- 文件夹的分割行 -->
          <div class="row">
          <span class="col" style="flex-basis: 1px; -webkit-box-flex: 1; flex-grow: 1; overflow: hidden;white-space: nowrap; text-overflow: ellipsis;">Folders</span>
          <div class="col text-end">


          <!-- Modal Button -->
          <button type="button"
          style="border: 0px; background: none; padding: 0; cursor: pointer;"
          class=""
          data-bs-toggle="modal"
          data-bs-target="#ModalFolderCreate"
          >
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <path d="M11.995 19.5a7.232 7.232 0 0 0 2.898-.585 7.582 7.582 0 0 0 2.392-1.627 7.748 7.748 0 0 0 1.625-2.398c.393-.904.59-1.868.59-2.89a7.172 7.172 0 0 0-.59-2.89 7.708 7.708 0 0 0-4.026-4.024 7.231 7.231 0 0 0-2.898-.586 7.209 7.209 0 0 0-2.888.586c-.905.39-1.7.932-2.387 1.626A7.788 7.788 0 0 0 5.09 9.11 7.172 7.172 0 0 0 4.5 12c0 1.022.197 1.986.59 2.89a7.748 7.748 0 0 0 1.625 2.398 7.582 7.582 0 0 0 2.392 1.627c.904.39 1.867.585 2.888.585Zm0-1.413c-.842 0-1.63-.156-2.363-.469A6.032 6.032 0 0 1 7.7 16.312a6.115 6.115 0 0 1-1.3-1.942A6.006 6.006 0 0 1 5.93 12a6.058 6.058 0 0 1 3.692-5.618 5.963 5.963 0 0 1 2.364-.47c.848 0 1.64.157 2.373.47.734.313 1.38.748 1.937 1.306a6.142 6.142 0 0 1 1.31 1.942c.315.737.473 1.527.473 2.37 0 .843-.156 1.633-.47 2.37a6.115 6.115 0 0 1-1.3 1.942 6.02 6.02 0 0 1-4.314 1.775ZM8.577 12c0 .204.065.37.195.497s.304.19.52.19H11.3v2.018c0 .204.062.373.186.506.123.133.29.2.501.2a.686.686 0 0 0 .506-.2.686.686 0 0 0 .2-.507v-2.016h2.006a.7.7 0 0 0 .511-.19.664.664 0 0 0 .195-.498.692.692 0 0 0-.195-.511.69.69 0 0 0-.51-.195h-2.007V9.286a.698.698 0 0 0-.2-.516.685.685 0 0 0-.506-.2.652.652 0 0 0-.502.2.728.728 0 0 0-.185.516v2.008H9.293a.703.703 0 0 0-.52.195c-.13.13-.196.3-.196.511Z" fill="#000"></path>
          </svg>
          </button>


          <!--
          <button type="button"
          style="border: 0px; background: none; padding: 0; cursor: pointer;"
          class=""
          data-bs-toggle="dropdown"
          aria-expanded="false"
          >
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <path d="M11.995 19.5a7.232 7.232 0 0 0 2.898-.585 7.582 7.582 0 0 0 2.392-1.627 7.748 7.748 0 0 0 1.625-2.398c.393-.904.59-1.868.59-2.89a7.172 7.172 0 0 0-.59-2.89 7.708 7.708 0 0 0-4.026-4.024 7.231 7.231 0 0 0-2.898-.586 7.209 7.209 0 0 0-2.888.586c-.905.39-1.7.932-2.387 1.626A7.788 7.788 0 0 0 5.09 9.11 7.172 7.172 0 0 0 4.5 12c0 1.022.197 1.986.59 2.89a7.748 7.748 0 0 0 1.625 2.398 7.582 7.582 0 0 0 2.392 1.627c.904.39 1.867.585 2.888.585Zm0-1.413c-.842 0-1.63-.156-2.363-.469A6.032 6.032 0 0 1 7.7 16.312a6.115 6.115 0 0 1-1.3-1.942A6.006 6.006 0 0 1 5.93 12a6.058 6.058 0 0 1 3.692-5.618 5.963 5.963 0 0 1 2.364-.47c.848 0 1.64.157 2.373.47.734.313 1.38.748 1.937 1.306a6.142 6.142 0 0 1 1.31 1.942c.315.737.473 1.527.473 2.37 0 .843-.156 1.633-.47 2.37a6.115 6.115 0 0 1-1.3 1.942 6.02 6.02 0 0 1-4.314 1.775ZM8.577 12c0 .204.065.37.195.497s.304.19.52.19H11.3v2.018c0 .204.062.373.186.506.123.133.29.2.501.2a.686.686 0 0 0 .506-.2.686.686 0 0 0 .2-.507v-2.016h2.006a.7.7 0 0 0 .511-.19.664.664 0 0 0 .195-.498.692.692 0 0 0-.195-.511.69.69 0 0 0-.51-.195h-2.007V9.286a.698.698 0 0 0-.2-.516.685.685 0 0 0-.506-.2.652.652 0 0 0-.502.2.728.728 0 0 0-.185.516v2.008H9.293a.703.703 0 0 0-.52.195c-.13.13-.196.3-.196.511Z" fill="#000"></path>
          </svg>
          </button>
          <ul class="dropdown-menu">
          <li>
          <a class="dropdown-item" href="#">
          Create new subfolder
          </a>
          </li>
          <li>
          <a class="dropdown-item" href="#">
          Edit
          </a>
          </li>
          <li>
          <a class="dropdown-item" href="#">
          Move to
          </a>
          </li>
          <li><hr class="dropdown-divider"></li>
          <li>
          <a class="dropdown-item" href="#" style="color: red;">
          Delete
          </a>
          </li>
          </ul>
          -->


          </div>
          </div>


          <!-- 第一组「树形菜单」 -->
          <li class="mb-1">
          <button class="btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed"
          data-bs-toggle="collapse" data-bs-target="#home-collapse"
          aria-expanded="true"
          >
          Home
          </button>


          <div class="collapse show" id="home-collapse">
          <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small">
          <li><a href="#" class="link-dark d-inline-flex text-decoration-none rounded">Overview</a></li>
          <li><a href="#" class="link-dark d-inline-flex text-decoration-none rounded">Updates</a></li>
          <li><a href="#" class="link-dark d-inline-flex text-decoration-none rounded">Reports</a></li>
          </ul>
          </div>
          </li>


          </ul>


          </div>


          <!-- Modal -->
          <div class="modal fade ModalFolderCreate"
          id="ModalFolderCreate"
          role="dialog"
          tabindex="-1"
          aria-labelledby="ModalFolderCreateLabel"
          aria-hidden="true"
          data-bs-backdrop="static" data-bs-keyboard="false"
          >


          <!-- Modal Dialog -->
          <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">


          <div class="modal-content">


          <!-- 头部区域 -->
          <div class="modal-header">
          <h4 class="modal-title fs-5"
          id="ModalFolderCreateLabel"
          >
          Create New Folder
          </h4>
          <button type="button" class="btn-close"
          data-bs-dismiss="modal"
          aria-label="Close"
          ></button>
          </div>


          <!-- 内容区域 -->
          <div class="modal-body">


          <form id="FormFolderCreateLabel">


          <!-- 文件夹名称 -->
          <div class="form-group">
          <input class="form-control" type="text" placeholder="Folder Name">
          <span class="error-msg"></span>
          <br>
          </div>


          <!-- 文件夹属性 -->


          </form>


          </div>


          <!-- 底部区域 -->
          <div class="modal-footer">
          <!--
          <button type="button"
          class="btn btn-secondary"
          data-bs-dismiss="modal"
          >
          取消
          </button>
          -->


          <button type="button"
          class="btn btn-primary"
          id="btnSubmitModalFolderCreate"
          >
          Create
          </button>
          </div>


          </div>


          </div>


          </div>


          <!-- 分割 -->
          <!--
          <div class="b-example-divider b-example-vr"></div>
          -->


          <!-- 右侧 -->
          <div class="container-fluid">


          <!-- 右侧头部:工具栏(按钮) -->
          <!--
          <div class="right-top-toolbar" style="margin-bottom: 5px"></div>
          -->


          <!-- 右侧富文本 -->
          <!-- 右侧头部:工具栏(按钮) -->
          <div>
          <!-- Button: Save -->
          <a type="button"
          id="btnWikiAddSubmit"
          class="btn" style="margin-top: 2px; margin-left: 2px; background-color: deepskyblue;"
          >
          <span>
          <svg t="1714058006123" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23526" width="24" height="24"><path d="M794.3 422.5c-11 0-20-9-20-20v-41.8L666.6 252.6v59.1c0 32.2-26.2 58.4-58.4 58.4h-218c-32.2 0-58.4-26.2-58.4-58.4V204.2c0-11 9-20 20-20s20 9 20 20v107.5c0 10.1 8.2 18.4 18.4 18.4h218c10.1 0 18.4-8.2 18.4-18.4V204.2c0-8.1 4.9-15.4 12.4-18.5 7.5-3.1 16.1-1.4 21.8 4.4l147.7 148.2c3.7 3.7 5.8 8.8 5.8 14.1v50.1c0 11.1-9 20-20 20z" fill="#1C1C1C" p-id="23527"></path><path d="M451.1 815.5h-208c-32.2 0-58.4-26.2-58.4-58.4V242.6c0-32.2 26.2-58.4 58.4-58.4h403.5c11 0 20 9 20 20s-9 20-20 20H243.1c-10.1 0-18.4 8.2-18.4 18.4v514.5c0 10.1 8.2 18.4 18.4 18.4h208c11 0 20 9 20 20s-9 20-20 20z" fill="#1C1C1C" p-id="23528"></path><path d="M304.4 814.6c-11 0-20-9-20-20V638c0-32.2 26.2-58.4 58.4-58.4h255c11 0 20 9 20 20s-9 20-20 20h-255c-10.1 0-18.4 8.2-18.4 18.4v156.7c0 10.9-9 19.9-20 19.9zM653.1 838.8c-5.1 0-10.2-2-14.1-5.9-7.8-7.8-7.8-20.5 0-28.3l230.4-230.4c4.8-4.8 4.8-12.7 0-17.5l-42.1-42.1c-4.7-4.7-12.8-4.7-17.5 0L579.3 745c-7.8 7.8-20.5 7.8-28.3 0-7.8-7.8-7.8-20.5 0-28.3l230.4-230.4c9.9-9.9 23-15.3 37-15.3s27.1 5.4 37 15.3l42.1 42.1c20.4 20.4 20.4 53.7 0 74.1L667.2 832.9c-3.9 3.9-9 5.9-14.1 5.9z" fill="#1C1C1C" p-id="23529"></path><path d="M539.6 864.2c-1.5 0-2.9-0.2-4.4-0.5-10.8-2.4-17.5-13.1-15.1-23.9l25.7-113.3c2.4-10.8 13.1-17.5 23.9-15.1 10.8 2.4 17.5 13.1 15.1 23.9L559 848.6c-2.1 9.3-10.3 15.6-19.4 15.6z" fill="#1C1C1C" p-id="23530"></path><path d="M539.5 864.2c-9.1 0-17.4-6.3-19.5-15.6-2.4-10.8 4.3-21.5 15.1-23.9L648.4 799c10.8-2.5 21.5 4.3 23.9 15.1 2.4 10.8-4.3 21.5-15.1 23.9L544 863.7c-1.5 0.3-3 0.5-4.5 0.5zM858.1 633.3c-5.1 0-10.2-2-14.1-5.9l-87.7-87.7c-7.8-7.8-7.8-20.5 0-28.3 7.8-7.8 20.5-7.8 28.3 0l87.7 87.7c7.8 7.8 7.8 20.5 0 28.3-4 4-9.1 5.9-14.2 5.9z" fill="#1C1C1C" p-id="23531"></path></svg>
          </span>
          Save
          </a>


          <!-- Button: Delete -->
          <a type="button"
          class="btn" style="margin-top: 2px; margin-left: 2px; background-color: red;"
          href="#"
          >
          <span>
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.692 20h6.625c.543 0 .98-.158 1.31-.474.33-.316.51-.748.54-1.295l.468-10.22h.76c.165 0 .307-.06.426-.182A.592.592 0 0 0 19 7.401a.593.593 0 0 0-.179-.429.576.576 0 0 0-.426-.182H5.605a.58.58 0 0 0-.422.182.585.585 0 0 0-.183.429c0 .164.061.306.183.428a.58.58 0 0 0 .422.182h.76l.477 10.23c.024.547.2.977.526 1.29.327.313.769.469 1.324.469Zm.12-1.222a.649.649 0 0 1-.477-.191.73.73 0 0 1-.21-.492L7.647 8.01h8.677l-.45 10.084a.697.697 0 0 1-.2.492.672.672 0 0 1-.496.191H8.812Zm1.072-1.13a.523.523 0 0 0 .37-.132.42.42 0 0 0 .133-.342l-.21-7.476a.442.442 0 0 0-.142-.346.521.521 0 0 0-.362-.128.506.506 0 0 0-.367.132.453.453 0 0 0-.137.342l.21 7.476a.479.479 0 0 0 .147.346c.092.085.21.128.358.128Zm2.116 0a.536.536 0 0 0 .38-.132.446.446 0 0 0 .142-.342V9.698a.446.446 0 0 0-.142-.342.536.536 0 0 0-.38-.132.54.54 0 0 0-.376.132.44.44 0 0 0-.146.342v7.476c0 .14.049.253.146.342a.54.54 0 0 0 .376.132Zm2.126 0c.14 0 .258-.043.352-.128a.47.47 0 0 0 .152-.346l.201-7.476a.419.419 0 0 0-.133-.342.523.523 0 0 0-.37-.132.504.504 0 0 0-.358.128.479.479 0 0 0-.147.346l-.201 7.476a.426.426 0 0 0 .128.342.52.52 0 0 0 .376.132ZM8.802 7.364h1.301V5.832a.59.59 0 0 1 .18-.45.66.66 0 0 1 .47-.17h2.484c.19 0 .344.057.463.17.119.112.178.262.178.45v1.532h1.31V5.741c0-.54-.166-.966-.499-1.276-.333-.31-.786-.465-1.36-.465h-2.676c-.574 0-1.026.155-1.356.465-.33.31-.495.735-.495 1.276v1.623Z" fill="#000"></path></svg>
          </span>
          Delete
          </a>


          <!-- Button: Others -->


          </div>


          <!-- 右侧编辑器 -->
          <div class="row">
          <div class="document-editor__toolbar"></div>
          </div>
          <div class="row-editor" style="width: 100%;">
          <div class="editor-container">
          <div class="editor"></div>
          </div>
          </div>


          </div>


          </main>
          {% endblock %}


          {% block js %}
          <script type="text/javascript" src="{% static 'js/project/manage/sidebars.js' %}"></script>
          <script type="text/javascript" src="{% static 'plugin/ckeditor/ckeditor.js' %}"></script>
          <script type="text/javascript" src="{% static 'plugin/ckeditor/config.project.wiki.add.js' %}"></script>


          <script>
          // 页面框架加载完成后自动执行的函数
          $(function() {
          btnWikiAddSubmitClick();
          });


          function btnWikiAddSubmitClick() {
          $('#btnWikiAddSubmit').click(function(){
          console.log("「保存」被点击了")


          })
          }
          </script>
          {% endblock %}



          05

          在Django中使用CKEditor「后台Admin管理页面


          再来看,如何在Django后台的Admin管理页面中使用CKEditor。


          先看没有使用CKEditor的时候的效果:

          可以看到,正文部分是一个输入框,并没有启用「富文本编辑器」


          然后用「开发者模式」定位上面的输入框,找到它对应的「ID」:

          可以看到,目标的ID为「id_wiki_content


          文件「config.project.wiki.admin.js」

            ClassicEditor
            .create( document.querySelector( '.field-wiki_content' ) )
            .catch( error => {
            console.error( error );
            } );


            修改「admin.py」

              @admin.register(Wiki)
              class WikiAdmin(admin.ModelAdmin):
              fields = [
              'wiki_creator','project',
              'wiki_name',
              'wiki_type',
              'wiki_parent',
              'wiki_content',
              ]
              list_display = [
              'wiki_creator', 'project',
              'wiki_name',
              'wiki_type',
              'wiki_parent',
              'wiki_content',
              'wiki_datetime_create',
              ]
                  
                  # 如果通过PIP的方式实现的CKEditor引入
                  # 则需要注释「class Media」这一段代码
              class Media:
              # 引入CSS文件
              css = {
              'all': ('css/ckeditor/ckeditor.css',)
              }
              # 引入JS文件
              js = (
              'plugin/jQuery/jquery-3.7.1.min.js',
              'plugin/ckeditor/ckeditor.js',
              'plugin/ckeditor/translations/zh.js',
              'plugin/ckeditor/config.project.wiki.admin.js',
              )



              06

              通过PIP装包的方式继承CKEditor


              安装软件包:

                (daily_info_management) (base) adamhuan@Leviathan daily_info_management % pwd
                /Users/adamhuan/PycharmProjects/visible-abyss/daily_info_management
                (daily_info_management) (base) adamhuan@Leviathan daily_info_management %
                (daily_info_management) (base) adamhuan@Leviathan daily_info_management % pip list
                Package Version
                ------------------- -----------
                asgiref 3.8.1
                async-timeout 4.0.3
                Django 5.0.4
                django-redis 5.4.0
                djangorestframework 3.15.1
                Faker 24.9.0
                pillow 10.3.0
                pip 24.0
                python-dateutil 2.9.0.post0
                redis 5.0.3
                setuptools 69.1.1
                six 1.16.0
                sqlparse 0.4.4
                typing_extensions 4.11.0
                wheel 0.42.0
                (daily_info_management) (base) adamhuan@Leviathan daily_info_management %
                (daily_info_management) (base) adamhuan@Leviathan daily_info_management % pip install django-ckeditor-5
                Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
                Collecting django-ckeditor-5
                Downloading https://mirrors.aliyun.com/pypi/packages/b0/c4/22e7991cd1f97c3da00e687c1ab0868fac4520b7a9848cdb1e3261d94e86/django_ckeditor_5-0.2.12-py3-none-any.whl (2.1 MB)
                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 392.1 kB/s eta 0:00:00
                Requirement already satisfied: Django>=2.2 in Users/adamhuan/PycharmProjects/visible-abyss/python_virtual_env/daily_info_management/lib/python3.10/site-packages (from django-ckeditor-5) (5.0.4)
                Requirement already satisfied: Pillow in Users/adamhuan/PycharmProjects/visible-abyss/python_virtual_env/daily_info_management/lib/python3.10/site-packages (from django-ckeditor-5) (10.3.0)
                Requirement already satisfied: asgiref<4,>=3.7.0 in Users/adamhuan/PycharmProjects/visible-abyss/python_virtual_env/daily_info_management/lib/python3.10/site-packages (from Django>=2.2->django-ckeditor-5) (3.8.1)
                Requirement already satisfied: sqlparse>=0.3.1 in Users/adamhuan/PycharmProjects/visible-abyss/python_virtual_env/daily_info_management/lib/python3.10/site-packages (from Django>=2.2->django-ckeditor-5) (0.4.4)
                Requirement already satisfied: typing-extensions>=4 in Users/adamhuan/PycharmProjects/visible-abyss/python_virtual_env/daily_info_management/lib/python3.10/site-packages (from asgiref<4,>=3.7.0->Django>=2.2->django-ckeditor-5) (4.11.0)
                Installing collected packages: django-ckeditor-5
                Successfully installed django-ckeditor-5-0.2.12
                (daily_info_management) (base) adamhuan@Leviathan daily_info_management %


                注册应用:


                配置应用:文件「settings.py」

                  customColorPalette = [
                  {
                  'color': 'hsl(4, 90%, 58%)',
                  'label': 'Red'
                  },
                  {
                  'color': 'hsl(340, 82%, 52%)',
                  'label': 'Pink'
                  },
                  {
                  'color': 'hsl(291, 64%, 42%)',
                  'label': 'Purple'
                  },
                  {
                  'color': 'hsl(262, 52%, 47%)',
                  'label': 'Deep Purple'
                  },
                  {
                  'color': 'hsl(231, 48%, 48%)',
                  'label': 'Indigo'
                  },
                  {
                  'color': 'hsl(207, 90%, 54%)',
                  'label': 'Blue'
                  },
                  ]


                  CKEDITOR_5_CONFIGS = {
                  'default': {
                  'toolbar': ['heading', '|', 'bold', 'italic', 'link',
                  'bulletedList', 'numberedList', 'blockQuote', 'imageUpload', ],


                  },
                  'extends': {
                  'blockToolbar': [
                  'paragraph', 'heading1', 'heading2', 'heading3',
                  '|',
                  'bulletedList', 'numberedList',
                  '|',
                  'blockQuote',
                  ],
                  'toolbar': ['heading', '|', 'outdent', 'indent', '|', 'bold', 'italic', 'link', 'underline', 'strikethrough',
                  'code', 'subscript', 'superscript', 'highlight', '|', 'codeBlock', 'sourceEditing', 'insertImage',
                  'bulletedList', 'numberedList', 'todoList', '|', 'blockQuote', 'imageUpload', '|',
                  'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'mediaEmbed', 'removeFormat',
                  'insertTable', ],
                  'image': {
                  'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft',
                  'imageStyle:alignRight', 'imageStyle:alignCenter', 'imageStyle:side', '|'],
                  'styles': [
                  'full',
                  'side',
                  'alignLeft',
                  'alignRight',
                  'alignCenter',
                  ]


                  },
                  'table': {
                  'contentToolbar': ['tableColumn', 'tableRow', 'mergeTableCells',
                  'tableProperties', 'tableCellProperties'],
                  'tableProperties': {
                  'borderColors': customColorPalette,
                  'backgroundColors': customColorPalette
                  },
                  'tableCellProperties': {
                  'borderColors': customColorPalette,
                  'backgroundColors': customColorPalette
                  }
                  },
                  'heading': {
                  'options': [
                  {'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph'},
                  {'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1'},
                  {'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2'},
                  {'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3'}
                  ]
                  }
                  },
                  'list': {
                  'properties': {
                  'styles': 'true',
                  'startIndex': 'true',
                  'reversed': 'true',
                  }
                  }
                  }


                  「settings.py」中,与「CKEditor」有关的其他配置项:

                    MEDIA_URL = '/media/'  
                    MEDIA_ROOT = os.path.join(BASE_DIR,'media/')
                    CKEDITOR_JQUERY_URL = 'https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js'
                    CKEDITOR_UPLOAD_PATH = 'uploads/'  
                    CKEDITOR_IMAGE_BACKEND = 'pillow'
                    CKEDITOR_CONFIGS = {
                    'default': {
                    'toolbar': 'full',
                    'height':600,
                    'width':800,
                    },
                    }


                    配置路由(主路由文件)

                      """
                      URL configuration for daily_info_management project.


                      The `urlpatterns` list routes URLs to views. For more information please see:
                      https://docs.djangoproject.com/en/5.0/topics/http/urls/
                      Examples:
                      Function views
                      1. Add an import: from my_app import views
                      2. Add a URL to urlpatterns: path('', views.templates, name='templates')
                      Class-based views
                      1. Add an import: from other_app.views import Home
                      2. Add a URL to urlpatterns: path('', Home.as_view(), name='templates')
                      Including another URLconf
                      1. Import the include() function: from django.urls import include, path
                      2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
                      """
                      from django.contrib import admin
                      from django.urls import path, include


                      from django.conf import settings
                      from django.conf.urls.static import static


                      urlpatterns = [
                      # 首页
                      path("", include("home.urls", namespace="home")),
                      path("home/", include("home.urls", namespace="home")),


                      # 管理页
                      path("admin/", admin.site.urls),


                      # 账户
                      path("account/", include("account.urls",namespace="account")),


                      # 项目
                      path("project/", include("project.urls", namespace="project")),
                      ]


                      # CKEditor 5
                      urlpatterns += [
                      path("ckeditor5/", include('django_ckeditor_5.urls'), name="ck_editor_5_upload_file"),
                      ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)





                      最后,修改目标属性的模型类:

                        from django.db import models
                        from django_ckeditor_5.fields import CKEditor5Field


                        class Wiki(models.Model):
                        """
                        维基百科
                        """
                        class Meta:
                        verbose_name = "维基百科"
                        verbose_name_plural = verbose_name


                        project = models.ForeignKey(
                        verbose_name="所属项目",
                        to="Project",
                        on_delete=models.CASCADE
                        )
                        wiki_creator = models.ForeignKey(
                        verbose_name="创建者",
                        to=Account,
                        on_delete=models.DO_NOTHING
                        )
                        wiki_name = models.CharField(
                        verbose_name="名称",
                        max_length=32
                        )


                        WIKI_TYPE_CHOICES = (
                        (1, "文档 Document"),
                        (2, "文件夹 Folder"),
                        )
                        wiki_type = models.SmallIntegerField(
                        verbose_name="类型",
                        choices=WIKI_TYPE_CHOICES,
                        default=1
                        )


                        # 之前的
                        # wiki_content = models.TextField(
                        # verbose_name="正文"
                        # )
                        wiki_content = CKEditor5Field(
                        verbose_name="正文",
                        config_name='extends'
                        )




                        wiki_datetime_create = models.DateTimeField(
                        verbose_name="创建时间",
                        auto_now_add=True,
                        )


                        wiki_parent = models.ForeignKey(
                        verbose_name="父级",
                        to="Wiki",
                        on_delete=models.CASCADE,
                        null=True,
                        blank=True,
                        related_name="wiki_children",
                        )



                        由于修改了数据模型类的定义,因此要做「数据库的迁移」

                          (daily_info_management) (base) adamhuan@Leviathan daily_info_management % python manage.py makemigrations
                          System check identified some issues:


                          WARNINGS:
                          ?: (2_0.W001) Your URL pattern 'star/(?P<project_type>\w+)/(?P<project_id>\d+)' [name='star'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
                          ?: (2_0.W001) Your URL pattern 'unstar/(?P<project_type>\w+)/(?P<project_id>\d+)' [name='unstar'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
                          ?: (rest_framework.W001) You have specified a default PAGE_SIZE pagination rest_framework setting, without specifying also a DEFAULT_PAGINATION_CLASS.
                          HINT: The default for DEFAULT_PAGINATION_CLASS is None. In previous versions this was PageNumberPagination. If you wish to define PAGE_SIZE globally whilst defining pagination_class on a per-view basis you may silence this check.
                          ?: (urls.W005) URL namespace 'home' isn't unique. You may not be able to reverse all URLs in this namespace
                          Migrations for 'project':
                          project/migrations/0007_alter_wiki_wiki_content.py
                          - Alter field wiki_content on wiki
                          (daily_info_management) (base) adamhuan@Leviathan daily_info_management %
                          (daily_info_management) (base) adamhuan@Leviathan daily_info_management % python manage.py migrate
                          System check identified some issues:


                          WARNINGS:
                          ?: (2_0.W001) Your URL pattern 'star/(?P<project_type>\w+)/(?P<project_id>\d+)' [name='star'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
                          ?: (2_0.W001) Your URL pattern 'unstar/(?P<project_type>\w+)/(?P<project_id>\d+)' [name='unstar'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
                          ?: (rest_framework.W001) You have specified a default PAGE_SIZE pagination rest_framework setting, without specifying also a DEFAULT_PAGINATION_CLASS.
                          HINT: The default for DEFAULT_PAGINATION_CLASS is None. In previous versions this was PageNumberPagination. If you wish to define PAGE_SIZE globally whilst defining pagination_class on a per-view basis you may silence this check.
                          ?: (urls.W005) URL namespace 'home' isn't unique. You may not be able to reverse all URLs in this namespace
                          Operations to perform:
                          Apply all migrations: account, admin, auth, contenttypes, project, sessions
                          Running migrations:
                          Applying project.0007_alter_wiki_wiki_content... OK
                          (daily_info_management) (base) adamhuan@Leviathan daily_info_management %


                          最后,看看后台管理页面的效果:

                          可以看到,CKEditor的富文本编辑器已经生效。


                          ————————————————————

                          以上,便是如何在Django中集成CKEditor5的简单步骤。


                          而关于CKEditor与Django的结合,其实还有很多的话题与相关技术点可以探讨,我将会在后续的笔记中逐步更新。





                          END




                          温馨提示



                          如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。


                          文章转载自Nephilim,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论