├── pages ├── post │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── about │ ├── index.json │ ├── index.js │ ├── index.wxss │ └── index.wxml ├── posts │ ├── index.json │ ├── index.wxss │ ├── index.js │ └── index.wxml └── wallpaper │ ├── index.json │ ├── index.wxss │ ├── index.wxml │ └── index.js ├── app.js ├── host.sample.js ├── image ├── wp.png ├── help.png ├── help_g.png ├── pause.png ├── play.png ├── post.png ├── post_g.png └── wp_g.png ├── shots ├── 1004149033.jpeg ├── 1237539799.jpeg └── 525257970.jpeg ├── .gitignore ├── helper ├── request.js ├── wx.js └── html2wxml │ ├── html2wxml.wxss │ ├── html2wxml.wxml │ ├── html2json.js │ └── htmlparser.js ├── app.json ├── LICENSE └── README.md /pages/post/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/about/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/posts/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/wallpaper/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | App({ data: { post: {} } }) 2 | -------------------------------------------------------------------------------- /host.sample.js: -------------------------------------------------------------------------------- 1 | export default 'http://127.0.0.1:8000' 2 | -------------------------------------------------------------------------------- /image/wp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/wp.png -------------------------------------------------------------------------------- /image/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/help.png -------------------------------------------------------------------------------- /image/help_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/help_g.png -------------------------------------------------------------------------------- /image/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/pause.png -------------------------------------------------------------------------------- /image/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/play.png -------------------------------------------------------------------------------- /image/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/post.png -------------------------------------------------------------------------------- /image/post_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/post_g.png -------------------------------------------------------------------------------- /image/wp_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/image/wp_g.png -------------------------------------------------------------------------------- /shots/1004149033.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/shots/1004149033.jpeg -------------------------------------------------------------------------------- /shots/1237539799.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/shots/1237539799.jpeg -------------------------------------------------------------------------------- /shots/525257970.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoeiFy/Softtyms/HEAD/shots/525257970.jpeg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | project.config.json 3 | .vscode 4 | jsconfig.json 5 | typings/ 6 | host.js 7 | -------------------------------------------------------------------------------- /pages/about/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | data: { 3 | src: 'https://i.loli.net/2019/02/12/5c627e10e0179.png', 4 | }, 5 | 6 | onTap() { 7 | wx.previewImage({ current: '', urls: [this.data.src] }) 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /pages/wallpaper/index.wxss: -------------------------------------------------------------------------------- 1 | scroll-view { 2 | height: 100vh; 3 | padding: 3px 3px 0; 4 | box-sizing: border-box; 5 | } 6 | .main { 7 | display: flex; 8 | } 9 | .main view { 10 | flex: 1; 11 | } 12 | .main view:first-child { 13 | margin-right: 3px; 14 | } 15 | image { 16 | width: 100%; 17 | display: block; 18 | margin-bottom: 3px; 19 | } 20 | -------------------------------------------------------------------------------- /pages/about/index.wxss: -------------------------------------------------------------------------------- 1 | .about { 2 | padding: 30px 30px 70px; 3 | } 4 | .heading { 5 | margin-top: 20px; 6 | color: #333; 7 | font-size: 18px; 8 | } 9 | .content { 10 | color: #666; 11 | font-size: 14px; 12 | margin-top: 10px; 13 | line-height: 1.5; 14 | } 15 | .br { 16 | margin-top: 5px; 17 | } 18 | .gift { 19 | width: 80%; 20 | margin: 30px auto 0; 21 | } 22 | image { 23 | width: 100%; 24 | display: block; 25 | } 26 | -------------------------------------------------------------------------------- /pages/wallpaper/index.wxml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /helper/request.js: -------------------------------------------------------------------------------- 1 | import { loading, toast } from './wx' 2 | import host from '../host' 3 | 4 | export default function (params) { 5 | const { 6 | url, 7 | data = {}, 8 | method = 'GET', 9 | } = params 10 | 11 | loading.call(data.page) 12 | 13 | Object.keys(data).forEach((key) => { 14 | if (data[key] === null || data[key] === undefined) { 15 | delete data[key] 16 | } 17 | }) 18 | 19 | return new Promise((resolve) => { 20 | wx.request({ 21 | url: `${host}/wp-json/wp/v2${url}`, 22 | data, 23 | method, 24 | success(data) { 25 | loading(false) 26 | 27 | if (data.data) { 28 | return resolve(data) 29 | } 30 | return toast('请求数据错误') 31 | } 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pages/about/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 关 于 3 | 这个小程序是 素锦 网站的小程序版本,关于 素锦 网站,是一个关于文字和图片的网站,你可以访问 isujin.com 查看 4 | 相关人员 5 | 6 | @naco: 这是一个老板 7 | 8 | @myhnet: 比较懒 9 | 10 | @白纸扇: 是一个绅士,同时是 素锦 网站的作者 11 | 12 | @LoeiFy: Cannot read property 'info' of null 13 | 14 | 再次关于 15 | 16 | 这个小程序的名字是 Softtyms. 你可以在这里找到源码:github.com/LoeiFy/Softtyms 17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pages/posts/index.wxss: -------------------------------------------------------------------------------- 1 | scroll-view { 2 | height: 100vh; 3 | background: #eee; 4 | } 5 | .post { 6 | background: #fff; 7 | margin-bottom: 10px; 8 | } 9 | .cover { 10 | position: relative; 11 | overflow: hidden; 12 | } 13 | .cover::before { 14 | content: ''; 15 | position: absolute; 16 | width: 100%; 17 | height: 100%; 18 | left: 0; 19 | top: 0; 20 | background: rgba(0, 0, 0, .2); 21 | } 22 | image { 23 | display: block; 24 | width: 100%; 25 | } 26 | .title { 27 | position: absolute; 28 | color: rgba(255, 255, 255, .9); 29 | font-size: 18px; 30 | line-height: 1.4; 31 | box-sizing: border-box; 32 | padding: 0 30px; 33 | bottom: 20px; 34 | } 35 | .excerpt { 36 | color: #999; 37 | font-size: 14px; 38 | padding: 20px 30px 30px; 39 | line-height: 1.6; 40 | } 41 | -------------------------------------------------------------------------------- /pages/posts/index.js: -------------------------------------------------------------------------------- 1 | import { store, setState } from '../../helper/wx' 2 | import request from '../../helper/request' 3 | 4 | Page({ 5 | data: { 6 | posts: [], 7 | }, 8 | 9 | setState, 10 | 11 | page: 1, 12 | 13 | loading: false, 14 | 15 | totalPage: 0, 16 | 17 | onScrollBottom() { 18 | const { posts: current } = this.data 19 | 20 | if (this.loading || this.totalPage === this.page) { 21 | return 22 | } 23 | 24 | this.loading = true 25 | this.page += 1 26 | 27 | request({ url: '/posts', data: { page: this.page } }) 28 | .then(({ data }) => this.setState({ posts: current.concat(data) })) 29 | .then(() => this.loading = false) 30 | }, 31 | 32 | onLoad() { 33 | request({ url: '/posts' }) 34 | .then(({ data: posts, header }) => { 35 | this.totalPage = Number(header['X-WP-TotalPages']) 36 | this.setState({ posts }) 37 | }) 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /pages/posts/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | var unescape = function(text) { 3 | return text 4 | .split('

') 5 | .join('') 6 | .split('

') 7 | .join('') 8 | .split('
') 9 | .join('') 10 | .split(' ') 11 | .join('') 12 | .split('[…]') 13 | .join('') 14 | } 15 | module.exports.unescape = unescape 16 |
17 | 18 | 25 | 31 | 32 | 33 | {{item.title.rendered}} 34 | 35 | {{helper.unescape(item.excerpt.rendered)}} 36 | 37 | 38 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/posts/index", 4 | "pages/post/index", 5 | "pages/about/index", 6 | "pages/wallpaper/index" 7 | ], 8 | "window": { 9 | "backgroundTextStyle": "light", 10 | "navigationBarBackgroundColor": "#fff", 11 | "navigationBarTitleText": "素 锦", 12 | "navigationBarTextStyle": "black" 13 | }, 14 | "tabBar": { 15 | "backgroundColor": "#fff", 16 | "selectedColor": "#0076f9", 17 | "color": "#606e82", 18 | "list": [ 19 | { 20 | "pagePath": "pages/posts/index", 21 | "iconPath": "image/post_g.png", 22 | "selectedIconPath": "image/post.png", 23 | "text": "文 章" 24 | }, 25 | { 26 | "pagePath": "pages/wallpaper/index", 27 | "iconPath": "image/wp_g.png", 28 | "selectedIconPath": "image/wp.png", 29 | "text": "壁 纸" 30 | }, 31 | { 32 | "pagePath": "pages/about/index", 33 | "iconPath": "image/help_g.png", 34 | "selectedIconPath": "image/help.png", 35 | "text": "关 于" 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pages/post/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | {{post.title.rendered}} 9 | 10 | 时间 {{post.date}} 11 | 字数 {{post.content.total}} 12 | 大概 {{post.content.time}} 分钟 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present LoeiFy@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pages/post/index.wxss: -------------------------------------------------------------------------------- 1 | @import "../../helper/html2wxml/html2wxml.wxss"; 2 | 3 | .title { 4 | color: #333; 5 | font-size: 22px; 6 | margin: 0 30px 20px; 7 | text-align: center; 8 | line-height: 1.5; 9 | display: block; 10 | padding-top: 30px; 11 | } 12 | .info { 13 | color: #9b9b9b; 14 | display: flex; 15 | line-height: 1; 16 | justify-content: center; 17 | font-size: 13px; 18 | } 19 | .info view { 20 | margin: 0 5px; 21 | } 22 | .cover { 23 | width: 100%; 24 | display: block; 25 | margin-top: 20px; 26 | } 27 | .content { 28 | margin: 10px 20px 0; 29 | padding-bottom: 50px; 30 | } 31 | scroll-view { 32 | height: 100vh; 33 | position: relative; 34 | } 35 | .post { 36 | position: relative; 37 | } 38 | .button { 39 | position: absolute; 40 | bottom: 30px; 41 | right: 30px; 42 | width: 42px; 43 | height: 42px; 44 | padding: 1px; 45 | box-sizing: border-box; 46 | border-radius: 50%; 47 | background: rgba(0, 0, 0, .3); 48 | } 49 | .button.ready { 50 | background: rgba(0, 0, 0, .6); 51 | } 52 | .button.ready image { 53 | opacity: 1; 54 | } 55 | .button image { 56 | width: 40px; 57 | height: 40px; 58 | display: block; 59 | opacity: .7; 60 | } 61 | .rate { 62 | transition: all .1s linear; 63 | height: 2px; 64 | position: absolute; 65 | top: 0; 66 | z-index: 10; 67 | background: #666; 68 | } 69 | -------------------------------------------------------------------------------- /helper/wx.js: -------------------------------------------------------------------------------- 1 | const app = () => getApp() 2 | 3 | export const store = { 4 | set(key, data) { 5 | app().data[key] = data 6 | return Promise.resolve() 7 | }, 8 | get(key) { 9 | return app().data[key] 10 | }, 11 | } 12 | 13 | export const toast = (title) => { 14 | wx.showToast({ 15 | title, 16 | icon: 'none', 17 | duration: 1000, 18 | mask: true, 19 | }) 20 | } 21 | 22 | export const loading = function(show = true) { 23 | if (show) { 24 | if (this > 1) { 25 | wx.showNavigationBarLoading() 26 | } else { 27 | wx.showLoading({ mask: true }) 28 | } 29 | return 30 | } 31 | wx.hideLoading() 32 | wx.hideNavigationBarLoading() 33 | } 34 | 35 | export const setState = function(data) { 36 | this.setData(data) 37 | return Promise.resolve(data) 38 | } 39 | 40 | export class Audio { 41 | constructor(src) { 42 | this.playCall = () => null 43 | this.canPlay = () => null 44 | this.ctx = wx.createInnerAudioContext() 45 | this.ctx.autoplay = false 46 | this.ctx.obeyMuteSwitch = false 47 | this.ctx.src = encodeURI(src) 48 | this.ctx.onTimeUpdate(() => this.playCall(this.ctx.currentTime, this.ctx.duration)) 49 | this.ctx.onPlay(() => null) 50 | this.ctx.onCanplay(() => this.canPlay()) 51 | } 52 | 53 | set onPlay(fn) { 54 | this.playCall = fn 55 | } 56 | 57 | set onReady(fn) { 58 | this.canPlay = fn 59 | } 60 | 61 | play() { 62 | if (this.canPlay) { 63 | this.ctx.play() 64 | } 65 | } 66 | 67 | pause() { 68 | this.ctx.pause() 69 | } 70 | 71 | stop() { 72 | this.ctx.stop() 73 | } 74 | 75 | destroy() { 76 | this.ctx.destroy() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pages/wallpaper/index.js: -------------------------------------------------------------------------------- 1 | import request from '../../helper/request' 2 | import { setState, store } from '../../helper/wx' 3 | 4 | Page({ 5 | data: { 6 | items: [], 7 | }, 8 | 9 | items: [], 10 | 11 | setState, 12 | 13 | page: 1, 14 | 15 | totalPage: 0, 16 | 17 | loading: false, 18 | 19 | getRow(data) { 20 | const items = data//.filter(({ post }) => post) 21 | const left = [] 22 | const right = [] 23 | let leftHeight = 0 24 | let rightHeight = 0 25 | 26 | items.forEach((item) => { 27 | const { height } = item.media_details.sizes.thumbnail 28 | if (leftHeight > rightHeight) { 29 | right.push(item) 30 | rightHeight += height 31 | } else { 32 | left.push(item) 33 | leftHeight += height 34 | } 35 | }) 36 | 37 | return [left, right] 38 | }, 39 | 40 | onScrollBottom() { 41 | if (this.loading || this.totalPage === this.page) { 42 | return 43 | } 44 | 45 | this.loading = true 46 | this.page += 1 47 | 48 | request({ url: '/media', data: { 49 | media_type: 'image', 50 | page: this.page, 51 | per_page: 20, 52 | }}) 53 | .then(({ data }) => { 54 | this.items = this.items.concat(data) 55 | return Promise.resolve(this.items) 56 | }) 57 | .then((items) => this.setState({ items: this.getRow(items) })) 58 | .then(() => this.loading = false) 59 | }, 60 | 61 | onLoad() { 62 | request({ url: '/media', data: { media_type: 'image', per_page: 20 } }) 63 | .then(({ data: items, header }) => { 64 | this.totalPage = Number(header['X-WP-TotalPages']) 65 | this.items = items 66 | this.setState({ items: this.getRow(items) }) 67 | }) 68 | }, 69 | 70 | onTap({ target }) { 71 | const { src } = target.dataset 72 | wx.previewImage({ 73 | current: src, 74 | urls: this.items 75 | //.filter(({ post }) => post) 76 | .map(({ media_details }) => media_details.sizes.full.source_url) 77 | }) 78 | }, 79 | }) 80 | -------------------------------------------------------------------------------- /helper/html2wxml/html2wxml.wxss: -------------------------------------------------------------------------------- 1 | .h2, .h3, .p, .div, .table, .tbody, .tr, .td, .ul, .li { 2 | color: #666; 3 | } 4 | 5 | .h2 { 6 | display: block; 7 | font-size: 36rpx; 8 | color: #39f; 9 | font-weight: 400; 10 | } 11 | 12 | .h3 { 13 | display: block; 14 | margin: 30rpx 30rpx 0 0; 15 | font-size: 30rpx; 16 | font-weight: 400; 17 | } 18 | 19 | .ul { 20 | display: block; 21 | list-style-type: disc; 22 | padding: 0; 23 | margin: 0; 24 | line-height: 40rpx; 25 | -webkit-margin-before: 1em; 26 | -webkit-margin-after: 1em; 27 | -webkit-margin-start: 0px; 28 | -webkit-margin-end: 0px; 29 | -webkit-padding-start: 40rpx; 30 | } 31 | 32 | .li { 33 | position: relative; 34 | display: list-item; 35 | padding: 0; 36 | margin: 0; 37 | line-height: 40rpx; 38 | } 39 | 40 | .br { 41 | display: block; 42 | } 43 | 44 | .strong { 45 | font-weight: 500; 46 | font-size: 16px; 47 | } 48 | 49 | .p { 50 | font-size: 15px; 51 | display: block; 52 | line-height: 1.6; 53 | padding: 0; 54 | margin-top: 0; 55 | margin-bottom: 10px; 56 | border-top: none; 57 | border-left: none; 58 | border-bottom: none; 59 | -webkit-margin-before: 1em; 60 | -webkit-margin-after: 1em; 61 | -webkit-margin-start: 0; 62 | -webkit-margin-end: 0; 63 | } 64 | 65 | .div { 66 | display: block; 67 | line-height: 40rpx; 68 | } 69 | 70 | .table { 71 | display: block; 72 | width: 100%; 73 | border-collapse: collapse; 74 | overflow: auto; 75 | border-right: 2rpx solid #ededed; 76 | margin: 20rpx 0 20rpx; 77 | padding: 0; 78 | border-top: none; 79 | border-left: none; 80 | border-bottom: none; 81 | } 82 | 83 | .tbody { 84 | width: 100%; 85 | display: table; 86 | vertical-align: middle; 87 | border-color: inherit; 88 | } 89 | 90 | .tr { 91 | display: table-row; 92 | margin: 0; 93 | padding: 0; 94 | vertical-align: inherit; 95 | border-color: inherit; 96 | } 97 | 98 | .tr:first-child { 99 | border-top: 2rpx #ededed solid; 100 | } 101 | 102 | .td { 103 | display: table-cell; 104 | vertical-align: inherit; 105 | min-width: 200rpx; 106 | line-height: 40rpx; 107 | margin: 0; 108 | padding: 14rpx 10rpx 10rpx; 109 | border: 2rpx solid #ededed; 110 | border-right: none; 111 | border-top: none; 112 | font-size: 24rpx; 113 | text-align: justify; 114 | color: #666; 115 | } 116 | -------------------------------------------------------------------------------- /pages/post/index.js: -------------------------------------------------------------------------------- 1 | import { store, setState, Audio } from '../../helper/wx' 2 | import request from '../../helper/request' 3 | import { html2json } from '../../helper/html2wxml/html2json' 4 | 5 | const clearText = (text) => { 6 | const audio = /].*>[\s\S]+<\/audio>/g 7 | const img = //g 8 | const script = //g 9 | const entity = /&.*?;/g 10 | const br = /
/g 11 | 12 | return text 13 | .replace(audio, '') 14 | .replace(img, '') 15 | .replace(script, '') 16 | .replace(entity, '') 17 | .replace(br, '

') 18 | } 19 | 20 | const word = (text) => { 21 | const per = 400 22 | let total = 0 23 | for (let i=0; i < text.length; i += 1) { 24 | if (text.charCodeAt(i) > 127 || text.charCodeAt(i) === 94) { 25 | total += 1 26 | } 27 | } 28 | return { total, time: Math.floor(total / per) } 29 | } 30 | 31 | Page({ 32 | data: { 33 | post: {}, 34 | current: 0, 35 | total: 1, 36 | innerHTML: '', 37 | status: 'pause', 38 | audioReady: false, 39 | }, 40 | 41 | setState, 42 | 43 | audio: null, 44 | 45 | initAudio() { 46 | const { audio: src } = this.data.post 47 | 48 | if (!src) { 49 | return 50 | } 51 | 52 | this.audio = new Audio(src) 53 | this.audio.onPlay = (current, total) => { 54 | this.setState({ 55 | current, 56 | total, 57 | }) 58 | if (current === total) { 59 | this.setState({ status: 'pause' }) 60 | } 61 | } 62 | this.audio.onReady = () => this.setState({ audioReady: true }) 63 | }, 64 | 65 | onTap() { 66 | const { status, audioReady } = this.data 67 | 68 | if (!audioReady) { 69 | return 70 | } 71 | 72 | if (status === 'pause') { 73 | this.audio.play() 74 | this.setState({ status: 'play' }) 75 | } else { 76 | this.audio.pause() 77 | this.setState({ status: 'pause' }) 78 | } 79 | }, 80 | 81 | onUnload() { 82 | this.audio.stop() 83 | this.audio.destroy() 84 | this.audio = null 85 | }, 86 | 87 | onLoad({ id }) { 88 | const posts = store.get('post') 89 | 90 | if (posts[id]) { 91 | this.setState({ post: posts[id], innerHTML: posts[id].innerHTML }) 92 | this.initAudio() 93 | return 94 | } 95 | 96 | request({ url: `/posts/${id}` }) 97 | .then(({ data: post }) => { 98 | const { rendered } = post.content 99 | 100 | post.content.rendered = clearText(rendered) 101 | post.content = { ...word(rendered), ...post.content } 102 | post.date = post.date.split('T')[0] 103 | post.innerHTML = html2json(post.content.rendered).child 104 | 105 | return this.setState({ post, innerHTML: post.innerHTML }) 106 | }) 107 | .then(({ post }) => { 108 | this.initAudio() 109 | posts[id] = post 110 | store.set('post', posts) 111 | }) 112 | }, 113 | }) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Softtyms 2 | 3 | Softtyms 是一个基于 WordPress [REST API](https://developer.wordpress.org/rest-api/) 的微信小程序 4 | 5 |
6 | 7 | > 由于域名即将失效,网站不继续维护,看不了预览,只能看截图 8 | > 9 | > https://github.com/LoeiFy/Softtyms/tree/master/shots 10 | 11 |
12 | 13 | ![gh_7107a625fda9_344](https://user-images.githubusercontent.com/2193211/42122301-3b0b4ba4-7c72-11e8-80a0-f7b62ae03f9b.jpg) 14 | 15 | ### 开发相关 16 | 17 | 修改 `host.sample.js` 为 `host.js`,填入你的 WordPress 网站地址 18 | 19 | ```js 20 | export default 'http://127.0.0.1:8000' 21 | ``` 22 | 23 | 需要修改图片地址 24 | 25 | ```html 26 | 27 | 28 | 29 | ``` 30 | 31 | ```html 32 | 33 | 34 | 35 | ``` 36 | 37 | 添加项目到微信小程序开发工具即可运行 38 | 39 | ### WordPress 设置 40 | 41 | 需要用到的 API 接口 42 | 43 | - /wp/v2/posts 44 | - /wp/v2/posts/(?P[\d]+) 45 | - /wp/v2/media 46 | 47 | 在主题 `function.php` 添加以下代码(php 我不大会) 48 | 49 | ```php 50 | function dw_rest_prepare_post( $data, $post, $request ) { 51 | 52 | $_data = $data->data; 53 | 54 | $params = $request->get_params(); 55 | 56 | if ( isset( $params['id'] ) ) { 57 | unset( $_data['excerpt'] ); 58 | } 59 | 60 | if ( ! isset( $params['id'] ) ) { 61 | unset( $_data['content'] ); 62 | } 63 | 64 | $thumbnail_id = get_post_thumbnail_id( $post->ID ); 65 | $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'full' ); 66 | $_data['thumbnail'] = $thumbnail[0]; 67 | 68 | if ( isset( $params['id'] ) ) { 69 | $media = get_attached_media( 'audio', $post->ID ); 70 | $keys = array_keys( $media ); 71 | $audio = wp_get_attachment_url( $keys[0] ); 72 | $_data['audio'] = $audio; 73 | } 74 | 75 | unset( $_data['date_gmt'] ); 76 | unset( $_data['featured_media'] ); 77 | unset( $_data['ping_status'] ); 78 | unset( $_data['comment_status'] ); 79 | unset( $_data['sticky'] ); 80 | unset( $_data['template'] ); 81 | unset( $_data['link'] ); 82 | unset( $_data['guid'] ); 83 | unset( $_data['modified_gmt'] ); 84 | unset( $_data['meta'] ); 85 | unset( $_data['modified'] ); 86 | unset( $_data['slug'] ); 87 | unset( $_data['type'] ); 88 | unset( $_data['author'] ); 89 | unset( $_data['categories'] ); 90 | unset( $_data['tags'] ); 91 | unset( $_data['status'] ); 92 | 93 | $data->remove_link( 'collection' ); 94 | $data->remove_link( 'self' ); 95 | $data->remove_link( 'about' ); 96 | $data->remove_link( 'author' ); 97 | $data->remove_link( 'replies' ); 98 | $data->remove_link( 'version-history' ); 99 | $data->remove_link( 'https://api.w.org/featuredmedia' ); 100 | $data->remove_link( 'https://api.w.org/attachment' ); 101 | $data->remove_link( 'https://api.w.org/term' ); 102 | $data->remove_link( 'curies' ); 103 | 104 | $data->data = $_data; 105 | 106 | return $data; 107 | 108 | } 109 | 110 | function dw_rest_prepare_attachment( $data, $post, $request ) { 111 | 112 | $_data = $data->data; 113 | 114 | $data->remove_link( 'collection' ); 115 | $data->remove_link( 'self' ); 116 | $data->remove_link( 'about' ); 117 | $data->remove_link( 'author' ); 118 | $data->remove_link( 'replies' ); 119 | 120 | unset( $_data['date'] ); 121 | unset( $_data['date_gmt'] ); 122 | unset( $_data['guid'] ); 123 | unset( $_data['modified'] ); 124 | unset( $_data['modified_gmt'] ); 125 | unset( $_data['slug'] ); 126 | unset( $_data['status'] ); 127 | unset( $_data['type'] ); 128 | unset( $_data['link'] ); 129 | unset( $_data['title'] ); 130 | unset( $_data['source_url'] ); 131 | unset( $_data['caption'] ); 132 | unset( $_data['description'] ); 133 | unset( $_data['author'] ); 134 | unset( $_data['comment_status'] ); 135 | unset( $_data['ping_status'] ); 136 | unset( $_data['meta'] ); 137 | unset( $_data['template'] ); 138 | unset( $_data['alt_text'] ); 139 | unset( $_data['media_type'] ); 140 | unset( $_data['mime_type'] ); 141 | 142 | $data->data = $_data; 143 | 144 | return $data; 145 | 146 | } 147 | 148 | add_filter( 'rest_prepare_post', 'dw_rest_prepare_post', 10, 3 ); 149 | add_filter( 'rest_prepare_attachment', 'dw_rest_prepare_attachment', 10, 3 ); 150 | ``` 151 | 152 | ### 第三方插件 153 | 154 | https://github.com/skyFi/html2wxml 155 | 156 | 157 | ### License 158 | 159 | MIT 160 | -------------------------------------------------------------------------------- /helper/html2wxml/html2wxml.wxml: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /helper/html2wxml/html2json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import {HTMLParser, HTMLtoXML, HTMLtoDOM} from './htmlparser'; 4 | 5 | let DEBUG = false; 6 | var debug = DEBUG ? console.log.bind(console) : function () { 7 | }; 8 | 9 | function q(v) { 10 | return '"' + v + '"'; 11 | } 12 | 13 | function removeDOCTYPE(html) { 14 | return html 15 | .replace(/<\?xml.*\?>\n/, '') 16 | .replace(/\n/, '') 17 | .replace(/\n/, ''); 18 | } 19 | 20 | export const html2json = function html2json(html) { 21 | html = (html || '').replace(/\s+/g, ' '); 22 | html = removeDOCTYPE(html); 23 | var bufArray = []; 24 | var results = { 25 | node: 'root', 26 | child: [], 27 | }; 28 | HTMLParser(html, { 29 | start: function (tag, attrs, unary) { 30 | debug(tag, attrs, unary); 31 | // node for this element 32 | var node = { 33 | node: 'element', 34 | tag: tag, 35 | }; 36 | 37 | if (attrs.length !== 0) { 38 | node.attr = attrs.reduce(function (pre, attr) { 39 | var name = attr.name; 40 | var value = attr.value; 41 | 42 | // 去掉空格 43 | value = value.replace(/\s/g, ''); 44 | value = value.replace(/( )/g, ' '); 45 | value = value.replace(/(>)/g, '>'); 46 | value = value.replace(/(<)/g, '<'); 47 | value = value.replace(/(&)/g, '&'); 48 | value = value.replace(/(")/g, '"'); 49 | // 多个值划分,单位转换 50 | let values = value.split(';'); 51 | value = (values || []).map(value => { 52 | if(value.indexOf('px') != -1) { 53 | let v = value.split(':')[1]; 54 | let key = value.split(':')[0] 55 | if(v.split(/\s/g).length > 1) { 56 | return v.map(vi => { 57 | let va = Number.parseInt(vi); 58 | if (!isNaN(va)) { 59 | va = va * 2; 60 | return `${key}: ${va}rpx;`; 61 | } 62 | return value; 63 | }); 64 | } else { 65 | let va = Number.parseInt(v); 66 | if (!isNaN(va)) { 67 | va = va * 2; 68 | return `${key}: ${va}rpx;`; 69 | } 70 | return value; 71 | } 72 | } else { 73 | return value; 74 | } 75 | }).join(' '); 76 | 77 | // if attr already exists 78 | // merge it 79 | if (pre[name]) { 80 | if (Array.isArray(pre[name])) { 81 | // already array, push to last 82 | pre[name].push(value); 83 | } else { 84 | // single value, make it array 85 | pre[name] = [pre[name], value]; 86 | } 87 | } else { 88 | // not exist, put it 89 | pre[name] = value; 90 | } 91 | 92 | return pre; 93 | }, {}); 94 | } 95 | 96 | // 标题样式等 97 | let tagClass = 98 | [ 99 | 'h1', 100 | 'h2', 101 | 'h3', 102 | 'h4', 103 | 'h5', 104 | 'h6', 105 | 'a', 106 | 'abbr', 107 | 'address', 108 | 'applet', 109 | 'acronym', 110 | 'area', 111 | 'article', 112 | 'aside', 113 | 'audio', 114 | 'b', 115 | 'base', 116 | 'basefont', 117 | 'bdi', 118 | 'bdo', 119 | 'big', 120 | 'blockquote', 121 | 'body', 122 | 'br', 123 | 'button', 124 | 'canvas', 125 | 'caption', 126 | 'center', 127 | 'cite', 128 | 'code', 129 | 'col', 130 | 'colgroup', 131 | 'command', 132 | 'datalist', 133 | 'dd', 134 | 'del', 135 | 'details', 136 | 'dfn', 137 | 'dir', 138 | 'div', 139 | 'dl', 140 | 'dt', 141 | 'em', 142 | 'embed', 143 | 'fieldset', 144 | 'figcaption', 145 | 'figure', 146 | 'font', 147 | 'footer', 148 | 'form', 149 | 'frame', 150 | 'frameset', 151 | 'h1', 152 | 'head', 153 | 'header', 154 | 'hgroup', 155 | 'hr', 156 | 'html', 157 | 'i', 158 | 'iframe', 159 | 'img', 160 | 'input', 161 | 'ins', 162 | 'keygen', 163 | 'kbd', 164 | 'label', 165 | 'legend', 166 | 'li', 167 | 'link', 168 | 'map', 169 | 'mark', 170 | 'menu', 171 | 'meta', 172 | 'meter', 173 | 'nav', 174 | 'noframes', 175 | 'noscript', 176 | 'object', 177 | 'ol', 178 | 'optgroup', 179 | 'option', 180 | 'output', 181 | 'p', 182 | 'param', 183 | 'pre', 184 | 'progress', 185 | 'q', 186 | 'rp', 187 | 'ruby', 188 | 's', 189 | 'samp', 190 | 'script', 191 | 'select', 192 | 'small', 193 | 'source', 194 | 'span', 195 | 'strike', 196 | 'strong', 197 | 'style', 198 | 'sub', 199 | 'summary', 200 | 'sup', 201 | 'table', 202 | 'tbody', 203 | 'td', 204 | 'textarea', 205 | 'tfoot', 206 | 'th', 207 | 'thead', 208 | 'time', 209 | 'title', 210 | 'tr', 211 | 'track', 212 | 'tt', 213 | 'u', 214 | 'ul', 215 | 'var', 216 | 'video', 217 | 'wbr', 218 | 'rt', 219 | 'section' 220 | ]; 221 | if(tagClass.includes(tag)) { 222 | if(node.attr && node.attr.class) { 223 | node.attr.class = `${node.attr.class} ${tag}` 224 | } else { 225 | node.attr = node.attr || {}; 226 | node.attr.class = tag; 227 | } 228 | } 229 | 230 | if (unary) { 231 | // if this tag dosen't have end tag 232 | // like 233 | // add to parents 234 | var parent = bufArray[0] || results; 235 | if (parent.child === undefined) { 236 | parent.child = []; 237 | } 238 | parent.child.push(node); 239 | } else { 240 | bufArray.unshift(node); 241 | } 242 | }, 243 | end: function (tag) { 244 | debug(tag); 245 | // merge into parent tag 246 | var node = bufArray.shift(); 247 | if (node.tag !== tag) console.error('invalid state: mismatch end tag'); 248 | 249 | if (bufArray.length === 0) { 250 | results.child.push(node); 251 | } else { 252 | var parent = bufArray[0]; 253 | if (parent.child === undefined) { 254 | parent.child = []; 255 | } 256 | parent.child.push(node); 257 | } 258 | }, 259 | chars: function (text) { 260 | debug(text); 261 | if(text == ' ') { 262 | return; 263 | } 264 | var node = { 265 | node: 'text', 266 | text: text, 267 | }; 268 | if (bufArray.length === 0) { 269 | results.child.push(node); 270 | } else { 271 | var parent = bufArray[0]; 272 | if (parent.child === undefined) { 273 | parent.child = []; 274 | } 275 | parent.child.push(node); 276 | } 277 | }, 278 | comment: function (text) { 279 | debug(text); 280 | var node = { 281 | node: 'comment', 282 | text: text, 283 | }; 284 | var parent = bufArray[0]; 285 | if (parent.child === undefined) { 286 | parent.child = []; 287 | } 288 | parent.child.push(node); 289 | }, 290 | }); 291 | return results; 292 | }; 293 | 294 | export const json2html = function json2html(json) { 295 | // Empty Elements - HTML 4.01 296 | var empty = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'embed']; 297 | 298 | var child = ''; 299 | if (json.child) { 300 | child = json.child.map(function (c) { 301 | return json2html(c); 302 | }).join(''); 303 | } 304 | 305 | var attr = ''; 306 | if (json.attr) { 307 | attr = Object.keys(json.attr).map(function (key) { 308 | var value = json.attr[key]; 309 | if (Array.isArray(value)) value = value.join(' '); 310 | return key + '=' + q(value); 311 | }).join(' '); 312 | if (attr !== '') attr = ' ' + attr; 313 | } 314 | 315 | if (json.node === 'element') { 316 | var tag = json.tag; 317 | if (empty.indexOf(tag) > -1) { 318 | // empty element 319 | return '<' + json.tag + attr + '/>'; 320 | } 321 | 322 | // non empty element 323 | var open = '<' + json.tag + attr + '>'; 324 | var close = ''; 325 | return open + child + close; 326 | } 327 | 328 | if (json.node === 'text') { 329 | return json.text; 330 | } 331 | 332 | if (json.node === 'comment') { 333 | return ''; 334 | } 335 | 336 | if (json.node === 'root') { 337 | return child; 338 | } 339 | }; 340 | -------------------------------------------------------------------------------- /helper/html2wxml/htmlparser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Regular Expressions for parsing tags and attributes 4 | var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, 5 | endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, 6 | attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; 7 | 8 | // Empty Elements - HTML 5 9 | var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"); 10 | 11 | // Block Elements - HTML 5 12 | var block = makeMap("a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); 13 | 14 | // Inline Elements - HTML 5 15 | var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); 16 | 17 | // Elements that you can, intentionally, leave open 18 | // (and which close themselves) 19 | var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); 20 | 21 | // Attributes that have their values filled in disabled="disabled" 22 | var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); 23 | 24 | // Special Elements (can contain anything) 25 | var special = makeMap("script,style"); 26 | 27 | export const HTMLParser = function (html, handler) { 28 | var index, chars, match, stack = [], last = html; 29 | stack.last = function () { 30 | return this[this.length - 1]; 31 | }; 32 | 33 | while (html) { 34 | chars = true; 35 | 36 | // Make sure we're not in a script or style element 37 | if (!stack.last() || !special[stack.last()]) { 38 | 39 | // Comment 40 | if (html.indexOf(""); 42 | 43 | if (index >= 0) { 44 | if (handler.comment) 45 | handler.comment(html.substring(4, index)); 46 | html = html.substring(index + 3); 47 | chars = false; 48 | } 49 | 50 | // end tag 51 | } else if (html.indexOf("]*>"), function (all, text) { 83 | text = text.replace(/|/g, "$1$2"); 84 | if (handler.chars) 85 | handler.chars(text); 86 | 87 | return ""; 88 | }); 89 | 90 | parseEndTag("", stack.last()); 91 | } 92 | 93 | if (html == last) 94 | throw "Parse Error: " + html; 95 | last = html; 96 | } 97 | 98 | // Clean up any remaining tags 99 | parseEndTag(); 100 | 101 | function parseStartTag(tag, tagName, rest, unary) { 102 | tagName = tagName.toLowerCase(); 103 | 104 | if (block[tagName]) { 105 | while (stack.last() && inline[stack.last()]) { 106 | parseEndTag("", stack.last()); 107 | } 108 | } 109 | 110 | if (closeSelf[tagName] && stack.last() == tagName) { 111 | parseEndTag("", tagName); 112 | } 113 | 114 | unary = empty[tagName] || !!unary; 115 | 116 | if (!unary) 117 | stack.push(tagName); 118 | 119 | if (handler.start) { 120 | var attrs = []; 121 | 122 | rest.replace(attr, function (match, name) { 123 | var value = arguments[2] ? arguments[2] : 124 | arguments[3] ? arguments[3] : 125 | arguments[4] ? arguments[4] : 126 | fillAttrs[name] ? name : ""; 127 | 128 | attrs.push({ 129 | name: name, 130 | value: value, 131 | escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //" 132 | }); 133 | }); 134 | 135 | if (handler.start) 136 | handler.start(tagName, attrs, unary); 137 | } 138 | } 139 | 140 | function parseEndTag(tag, tagName) { 141 | // If no tag name is provided, clean shop 142 | if (!tagName) 143 | var pos = 0; 144 | 145 | // Find the closest opened tag of the same type 146 | else 147 | for (var pos = stack.length - 1; pos >= 0; pos--) 148 | if (stack[pos] == tagName) 149 | break; 150 | 151 | if (pos >= 0) { 152 | // Close all the open elements, up the stack 153 | for (var i = stack.length - 1; i >= pos; i--) 154 | if (handler.end) 155 | handler.end(stack[i]); 156 | 157 | // Remove the open elements from the stack 158 | stack.length = pos; 159 | } 160 | } 161 | }; 162 | 163 | export const HTMLtoXML = function (html) { 164 | var results = ""; 165 | 166 | HTMLParser(html, { 167 | start: function (tag, attrs, unary) { 168 | results += "<" + tag; 169 | 170 | for (var i = 0; i < attrs.length; i++) 171 | results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; 172 | results += ">"; 173 | }, 174 | end: function (tag) { 175 | results += ""; 176 | }, 177 | chars: function (text) { 178 | results += text; 179 | }, 180 | comment: function (text) { 181 | results += ""; 182 | } 183 | }); 184 | 185 | return results; 186 | }; 187 | 188 | export const HTMLtoDOM = function (html, doc) { 189 | // There can be only one of these elements 190 | var one = makeMap("html,head,body,title"); 191 | 192 | // Enforce a structure for the document 193 | var structure = { 194 | link: "head", 195 | base: "head" 196 | }; 197 | 198 | if (!doc) { 199 | if (typeof DOMDocument != "undefined") 200 | doc = new DOMDocument(); 201 | else if (typeof document != "undefined" && document.implementation && document.implementation.createDocument) 202 | doc = document.implementation.createDocument("", "", null); 203 | else if (typeof ActiveX != "undefined") 204 | doc = new ActiveXObject("Msxml.DOMDocument"); 205 | 206 | } else 207 | doc = doc.ownerDocument || 208 | doc.getOwnerDocument && doc.getOwnerDocument() || 209 | doc; 210 | 211 | var elems = [], 212 | documentElement = doc.documentElement || 213 | doc.getDocumentElement && doc.getDocumentElement(); 214 | 215 | // If we're dealing with an empty document then we 216 | // need to pre-populate it with the HTML document structure 217 | if (!documentElement && doc.createElement) (function () { 218 | var html = doc.createElement("html"); 219 | var head = doc.createElement("head"); 220 | head.appendChild(doc.createElement("title")); 221 | html.appendChild(head); 222 | html.appendChild(doc.createElement("body")); 223 | doc.appendChild(html); 224 | })(); 225 | 226 | // Find all the unique elements 227 | if (doc.getElementsByTagName) 228 | for (var i in one) 229 | one[i] = doc.getElementsByTagName(i)[0]; 230 | 231 | // If we're working with a document, inject contents into 232 | // the body element 233 | var curParentNode = one.body; 234 | 235 | HTMLParser(html, { 236 | start: function (tagName, attrs, unary) { 237 | // If it's a pre-built element, then we can ignore 238 | // its construction 239 | if (one[tagName]) { 240 | curParentNode = one[tagName]; 241 | if (!unary) { 242 | elems.push(curParentNode); 243 | } 244 | return; 245 | } 246 | 247 | var elem = doc.createElement(tagName); 248 | 249 | for (var attr in attrs) 250 | elem.setAttribute(attrs[attr].name, attrs[attr].value); 251 | 252 | if (structure[tagName] && typeof one[structure[tagName]] != "boolean") 253 | one[structure[tagName]].appendChild(elem); 254 | 255 | else if (curParentNode && curParentNode.appendChild) 256 | curParentNode.appendChild(elem); 257 | 258 | if (!unary) { 259 | elems.push(elem); 260 | curParentNode = elem; 261 | } 262 | }, 263 | end: function (tag) { 264 | elems.length -= 1; 265 | 266 | // Init the new parentNode 267 | curParentNode = elems[elems.length - 1]; 268 | }, 269 | chars: function (text) { 270 | curParentNode.appendChild(doc.createTextNode(text)); 271 | }, 272 | comment: function (text) { 273 | // create comment node 274 | } 275 | }); 276 | 277 | return doc; 278 | }; 279 | 280 | function makeMap(str) { 281 | var obj = {}, items = str.split(","); 282 | for (var i = 0; i < items.length; i++) 283 | obj[items[i]] = true; 284 | return obj; 285 | } 286 | --------------------------------------------------------------------------------