Flask Builds Blog Built-in Third Party Markdown

There's always been a catch-up with your blog site, and then you find that article editing can cause a lot of problems if you just use text editing, so you decide to look for data and have a third-party open source Markdown editor built in.
Ultimately, I chose editor.md.

Show me the final result.

Say nothing more. Start today's learning journey.

1. Organizing ideas

After careful consideration, several technical problems were found:

  1. Built-in source code into flask and display on page
  2. After entering an article, you need to convert the text format of the article into a format that is stored in the database
  3. When viewing blog posts, read the text from the database and convert to the same browsing format as on the right side of the editor.
  4. Solve the problem of not inserting pictures into the article.

Below I will introduce you in the article "New", not "Edit".

2. Display Editor

Write the form for this page first:

class PostForm(FlaskForm):
    title = StringField('Title', [DataRequired(), length(max=255)])
    body = TextAreaField('content', [DataRequired()])
    #categories = SelectMultipleField('Categories', coerce=int)
    categories=SelectField('Articles', choices=[],coerce=int )
    body_html = HiddenField()
    submit=SubmitField(render_kw={'value': "Submit",'class': 'btn btn-success pull-right'})
    file = FileField(label="Article Cover",validators=[FileRequired(),FileAllowed(['png', 'jpg'], 'Receive only.png and.jpg Picture')])
    #Ensure data synchronization with database
    def __init__(self):
	    super(PostForm, self).__init__()
	    self.categories.choices = [(c.id, c.name) for c in Category.query.order_by('id')]

The u init_ function is used here because categories are flexible and synchronized with the article category table in the database.

Next on html:

<form class="am-form am-form-horizontal" method="post" action="" enctype="multipart/form-data">
		            {{ form.hidden_tag() }}
		            <div class="am-form-group am-form-file am-form-group-lg am-form-group-sm am-form-group-md">
		            	<div class="am-u-sm-4 am-u-md-4 am-u-lg-4">
		            		<button type="button" class="am-btn am-btn-dark am-btn-sm am-radius">
						    <i class="am-icon-cloud-upload"></i> Select article cover</button>
		            	
		            	{{ form.file(class_='form-control',id='doc-form-file') }}
		            	
						<div id="file-list"></div>
						<script>
						  $(function() {
						    $('#doc-form-file').on('change', function() {
						      var fileNames = '';
						      $.each(this.files, function() {
						        fileNames += '<span class="am-badge">' + this.name + '</span> ';
						      });
						      $('#file-list').html(fileNames);
						    });
						  });
						</script></div>
		            	
		            </div>
		            <div class="am-form-group am-form-group-lg am-form-group-sm am-form-group-md">
		            	<div class="am-u-sm-12 am-u-md-12 am-u-lg-12">
		            	
		            	{{ form.categories(class_="am-radius") }}

		            	</div>
		            </div>
		            <div class="am-form-group am-form-group-lg am-form-group-sm am-form-group-md">
		                <div class="am-u-sm-12 am-u-md-12 am-u-lg-12">
		                {% if form.title.errors %}
		                    {% for e in form.title.errors %}
		                        <p class="help-block">{{ e }}</p>
		                    {% endfor %}
		                {% endif %}
		                {{ form.title(class_="am-form-field am-radius",placeholder="Please enter a title") }}
		                </div>
		            </div>
		            <div class="am-form-group am-form-group-lg am-form-group-sm am-form-group-md">
		                <div class="am-u-sm-12 am-u-md-12 am-u-lg-12">
		                	{% if form.body.errors %}
		                    {% for e in form.body.errors %}
		                        <p class="help-block">{{ e }}</p>
		                    {% endfor %}
			                {% endif %}
			                <div id="editormd" class="form-control">
			                    {{ form.body(style="display:none;" ,class_="am-radius") }}
			                </div>
		                </div>
		            </div>
		            <div class="am-form-group">
				    <div class="am-u-sm-2 am-fr ">
				      <button type="submit" class="am-btn am-btn-default am-fr">Submit</button>
				    </div>
				  </div>
</form>

Where id="editormd" is the key to the editor display:

And you need to add a script at the end of the file:

<script src="{{ url_for('static',filename='editormd/editormd.min.js') }}"></script>
<script type="text/javascript">
    var testEditor;
    $(function () {
        testEditor = editormd("editormd", {
            //Width:'90%', #Do not set width here, otherwise it will not appear
        	height: 640,
            syncScrolling: "single",
            path: "{{ url_for('static',filename='editormd/lib/') }}",
        	theme : "dark",
            previewTheme : "dark",#Background color, you can also use light
            editorTheme : "pastel-on-dark",
            //markdown : md,
            codeFold : true,
            //syncScrolling : false,
            saveHTMLToTextarea : true,    // Save HTML to Textarea
            searchReplace : true,
            //Watch: false, //turn off live preview
            htmlDecode : "style,script,iframe|on*",            // Turn on HTML tag parsing, which is not turned on by default for security    
            //Toolbar: false, //close the toolbar
            //PreviewCodeHighlight: false, //off Preview HTML Code Block Highlight, turned on by default
            emoji : true,
            taskList : true,
            tocm            : true,         // Using [TOCM]
            tex : true,                   // Turn on TeX language support for scientific formulas, turn off by default
            flowChart : true,             // Turn on flow chart support, turn off by default
            sequenceDiagram : true,       // Turn on Timing/Sequence Diagram support, turn off by default,
            //DialogLockScreen: false, //Set the pop-up dialog to unlock the screen, global, default to true
            //DiaogShowMask: false, //Set pop-up dialog displays transparent mask layer, global, default to true
            //DiaogDraggable: false, //Set pop-up dialog is non-draggable, global, default to true
            //DiaogMaskOpacity: 0.4, //Set the transparency of the transparent mask layer, global, default 0.1
            //DialogMaskBgColor: "#000", //Set the background color of the transparent mask layer, global, default to #fff
            imageUpload : true,
            imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
            imageUploadURL : "{{ url_for('.upload') }}",
            onload : function() {
                console.log('onload', this);
                //this.fullscreen();
                //this.unwatch();
                //this.watch().fullscreen();
                //this.setMarkdown("#PHP");
                //this.width("100%");
                //this.height(480);
                //this.resize("100%", 640);
            }
        });
    });
</script>

Explain the above code after the code for picture upload:

3. Design databases and increase routing

You can also see what forms are on the interface of my blog, and here you have to design a database to store them.

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))       #Title
    body=db.Column(db.Text)     #Original text format
    body_html = db.Column(db.Text)      #Format after converting to html code
    create_time = db.Column(db.DateTime, index=True, default=datetime.utcnow)       #Creation Time
    seo_link = db.Column(db.String(128))        #Original article link
    pic_path = db.Column(db.String(320))        #Cover Address
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))     #Article Categories
    posts = db.relationship('Post',backref='article',lazy='dynamic')    #Foreign key for a comment

    #Convert text to html
    @staticmethod
    def on_changed_body(target, value, oldvalue, initiator):
        allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code',
                    'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul',
                    'h1', 'h2', 'h3', 'p', 'img', 'video', 'div', 'iframe', 'p', 'br', 'span', 'hr', 'src', 'class']
        allowed_attrs = {'*': ['class'],
                    'a': ['href', 'rel'],
                    'img': ['src', 'alt']}
        target.body_html = bleach.linkify(bleach.clean(
            markdown(value, output_format='html'),
            tags=allowed_tags, attributes=allowed_attrs, strip=True))
db.event.listen(Article.body, 'set', Article.on_changed_body)

Explain that the text in the editor is in the text format that you write. Stored in a database and stored in html format, you can record the layout of the article so that it can be displayed in the same format when you output the content of the article.
The code in the following figure is the key code for converting editor.md to html format.

Next, add routes to handle the logic

@app.route('/admin/post', methods=['GET', 'POST'])
@login_required
@csrf.exempt
def post():
    title="write an article"
    form = PostForm()
    if form.validate_on_submit():
	    #basepath = os.path.dirname(__file__)  # The path of the current file
	    #fileGet='uploads/assignment{}'.format(HOMEWORK_TIME)
	    #upload_path = os.path.join(basepath,fileGet,secure_filename(fpy.filename))
	    savepic_path = 'app/static/assets/img/'+form.file.data.filename
	    form.file.data.save(savepic_path)  #Processing Cover Addresses
	    cate=Category.query.filter_by(name=dict(form.categories.choices).get(form.categories.data)).first_or_404()  #Processing Categories
	    cate.number=cate.number+1
	    article=Article(title=form.title.data,body = form.body.data,create_time = datetime.now(),pic_path='static/assets/img/'+form.file.data.filename,category_id=cate.id)  #New Article
	    db.session.add(article)
	    db.session.commit()
	    flash('Upload successful!')
	    return redirect(url_for('index'))
    #if request.method=='POST':
    #	fpic=request.files['editormd-image-file']
    #	bodypic_path='app/static/pic/'+fpic.filename
    #	fpic.save(bodypic_path)
    return render_template('/admin/post.html',title=title, form=form,category=category)

4. Unable to Insert Pictures Solution

When I thought I was going to do a great job, I found that the editor's picture couldn't be inserted, so I looked at the source code, looked at the material, and finally found the problem.In the html page code above

This opens the Insert Picture function of the editor, and sets a view function to handle where the uploaded pictures go and the format in which they can be uploaded.
upload view function:

@app.route('/upload/',methods=['GET','POST'])
@login_required
@csrf.exempt
def upload():
    file=request.files.get('editormd-image-file')
    if not file:
	    res={
		    'success':0,
		    'message':'Upload failed'
	    }
    else:
	    ex=os.path.splitext(file.filename)[1]
	    filename=datetime.now().strftime('%Y%m%d%H%M%S')+ex
	    bodypic_path='app/static/pic/'+filename
	    file.save(bodypic_path)
	    res={
		    'success':1,
		    'message':'Upload Successful',
		    'url':url_for('.image',name=filename)
	    }
    return jsonify(res)

In the official editor.md document, the format in which the pictures are inserted is a json format, which we will work with.So why is file=request.files.get('editormd-image-file')?
When you insert a picture, a new box appears and we open the developer tool to see that the name of the from from which the picture was uploaded, we need this id and get the data, so there is such a file variable.

Click Submit.

The last wave of websites I haven't finished is:


There are still some problems with this side of the typesetting, so we can improve it later!

If you have questions or need wallpaper to chat with me in private on my WeChat public number, I will give you the most accurate answer, and because the personal blog is still under design, the domain name is also on file, and the source code will be published and made public later.Welcome to my blog duck!Your support is my greatest motivation!

Tags: Database Session Javascript emoji

Posted on Thu, 08 Aug 2019 19:40:07 -0700 by parka