diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9d42288e..7f6d3c92 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,7 @@ test:
- apt install firefox-esr -y --no-install-recommends
- firefox --version
- yarn
- - npm run unit
+ - yarn unit
build:
stage: build
diff --git a/CHANGELOG b/CHANGELOG
deleted file mode 100644
index ca2ebcfa..00000000
--- a/CHANGELOG
+++ /dev/null
@@ -1,35 +0,0 @@
-## 2017-02-20
-
-- Overall CSS styling fixes
-- Current theme is displayed in theme selector
-- Theme selector is moved to the settings page
-- Oembed attachments will now display correctly
-- Styling changes to the user info cards
-- Notification count in title
-- Better Notification handling (persistance, mark as read)
-- Post statuses with ctrl+enter
-- Links in statuses open in a new tab
-- Optimized mobile view
-- Fix crash on persistance failure
-- Compress persisted state
-- Sync mutes with backend (SEE NOTE BELOW)
-
-Pleroma will now try to get the current mutes from the backend. Sadly, a bug in
-Qvitter will not allow getting the mutes from the endpoint, because it will
-ignore HTTP Basic authentication. Mutes will still persist in Pleroma through
-localstorage, but the mutes from Qvitter won't be picked up if the call fails.
-
-The patch for Qvitter:
-
---- a/actions/apiqvittermutes.php
-+++ b/actions/apiqvittermutes.php
-@@ -74,7 +74,7 @@ class ApiQvitterMutesAction extends ApiPrivateAuthAction
- {
- parent::handle();
-
-- $this->target = Profile::current();
-+ $this->target = $this->scoped;
-
- if(!$this->target instanceof Profile) {
- $this->clientError(_('You have to be logged in to view your mutes.'), 403);
-
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index f8968966..5cc0135e 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -3,6 +3,7 @@ var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
+var FontelloPlugin = require("fontello-webpack-plugin")
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@@ -11,6 +12,8 @@ var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
+var now = Date.now()
+
module.exports = {
entry: {
app: './src/main.js'
@@ -90,6 +93,14 @@ module.exports = {
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
+ }),
+ new FontelloPlugin({
+ config: require('../static/fontello.json'),
+ name: 'fontello',
+ output: {
+ css: 'static/[name].' + now + '.css', // [hash] is not supported. Use the current timestamp instead for versioning.
+ font: 'static/font/[name].' + now + '.[ext]'
+ }
})
]
}
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index f7397a55..0a9bbd7a 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -77,6 +77,9 @@ Use custom image for NSFW'd images
### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors
+### `hideSitename`
+Hide instance name in header
+
## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
diff --git a/index.html b/index.html
index fd4e795e..1ff944d9 100644
--- a/index.html
+++ b/index.html
@@ -6,8 +6,6 @@
{{ $t('settings.show_admin_badge') }}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ead333c1..85146ef5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -370,6 +370,8 @@
"false": "no",
"true": "yes"
},
+ "fun": "Fun",
+ "greentext": "Meme arrows",
"notifications": "Notifications",
"notification_setting": "Receive notifications from:",
"notification_setting_follows": "Users you follow",
@@ -569,6 +571,7 @@
"followers": "Followers",
"following": "Following!",
"follows_you": "Follows you!",
+ "hidden": "Hidden",
"its_you": "It's you!",
"media": "Media",
"mention": "Mention",
diff --git a/src/i18n/ja.json b/src/i18n/ja_easy.json
similarity index 100%
rename from src/i18n/ja.json
rename to src/i18n/ja_easy.json
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 89c8a8c8..c56ae205 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -23,8 +23,8 @@ const messages = {
he: require('./he.json'),
hu: require('./hu.json'),
it: require('./it.json'),
- ja: require('./ja.json'),
- ja_pedantic: require('./ja_pedantic.json'),
+ ja: require('./ja_pedantic.json'),
+ ja_easy: require('./ja_easy.json'),
ko: require('./ko.json'),
nb: require('./nb.json'),
nl: require('./nl.json'),
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index f8bcd996..19e10f1e 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -174,6 +174,8 @@
"name_bio": "Имя и описание",
"new_email": "Новый email",
"new_password": "Новый пароль",
+ "fun": "Потешное",
+ "greentext": "Мемные стрелочки",
"notification_visibility": "Показывать уведомления",
"notification_visibility_follows": "Подписки",
"notification_visibility_likes": "Лайки",
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 80c4e0d8..8d9462ab 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -111,7 +111,7 @@
},
"interactions": {
"favs_repeats": "转发和收藏",
- "follows": "新的关注着",
+ "follows": "新的关注者",
"load_older": "加载更早的互动"
},
"post_status": {
diff --git a/src/modules/api.js b/src/modules/api.js
index eb6a7980..1293e3c8 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -43,6 +43,13 @@ const api = {
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
+ startFetchingFollowRequest (store) {
+ // Don't start fetching if we already are.
+ if (store.state.fetchers['followRequest']) return
+
+ const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
+ store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+ },
stopFetching (store, fetcherName) {
const fetcher = store.state.fetchers[fetcherName]
window.clearInterval(fetcher)
diff --git a/src/modules/config.js b/src/modules/config.js
index d4819ee8..329b4091 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -45,6 +45,7 @@ export const defaultState = {
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: false,
+ greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined // instance default
}
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 7b0e0da4..625323b9 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -27,11 +27,13 @@ const defaultState = {
scopeCopy: true,
subjectLineBehavior: 'email',
postContentType: 'text/plain',
+ hideSitename: false,
nsfwCensorImage: undefined,
vapidPublicKey: undefined,
noAttachmentLinks: false,
showFeaturesPanel: true,
minimalScopesMode: false,
+ greentext: false,
// Nasty stuff
pleromaBackend: true,
diff --git a/src/modules/users.js b/src/modules/users.js
index 1c9ff5e8..14b2d8b5 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -434,6 +434,7 @@ const users = {
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetching', 'notifications')
+ store.dispatch('stopFetching', 'followRequest')
store.commit('clearNotifications')
store.commit('resetStatuses')
})
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..68be0d50 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -22,7 +22,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
-const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp'
+const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index d6617276..c16bd1f1 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,6 +1,7 @@
import apiService from '../api/api.service.js'
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
+import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
const backendInteractorService = credentials => {
const fetchStatus = ({ id }) => {
@@ -63,6 +64,10 @@ const backendInteractorService = credentials => {
return notificationsFetcher.startFetching({ store, credentials })
}
+ const startFetchingFollowRequest = ({ store }) => {
+ return followRequestFetcher.startFetching({ store, credentials })
+ }
+
// eslint-disable-next-line camelcase
const tagUser = ({ screen_name }, tag) => {
return apiService.tagUser({ screen_name, tag, credentials })
@@ -111,7 +116,6 @@ const backendInteractorService = credentials => {
const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
const fetchBlocks = () => apiService.fetchBlocks({ credentials })
- const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
@@ -168,6 +172,7 @@ const backendInteractorService = credentials => {
verifyCredentials: apiService.verifyCredentials,
startFetchingTimeline,
startFetchingNotifications,
+ startFetchingFollowRequest,
fetchMutes,
muteUser,
unmuteUser,
@@ -203,7 +208,6 @@ const backendInteractorService = credentials => {
mfaSetupOTP,
mfaConfirmOTP,
mfaDisableOTP,
- fetchFollowRequests,
approveUser,
denyUser,
vote,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 5f45660d..ca79df6f 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -46,6 +46,14 @@ export const parseUser = (data) => {
output.description = data.note
output.description_html = addEmojis(data.note, data.emojis)
+ output.fields = data.fields
+ output.fields_html = data.fields.map(field => {
+ return {
+ name: addEmojis(field.name, data.emojis),
+ value: addEmojis(field.value, data.emojis)
+ }
+ })
+
// Utilize avatar_static for gif avatars?
output.profile_image_url = data.avatar
output.profile_image_url_original = data.avatar
@@ -95,6 +103,7 @@ export const parseUser = (data) => {
if (data.source) {
output.description = data.source.note
output.default_scope = data.source.privacy
+ output.fields = data.source.fields
if (data.source.pleroma) {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
new file mode 100644
index 00000000..de6f20ef
--- /dev/null
+++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
@@ -0,0 +1,94 @@
+/**
+ * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and
+ * allows it to be processed, useful for greentexting, mostly
+ *
+ * known issue: doesn't handle CDATA so nested CDATA might not work well
+ *
+ * @param {Object} input - input data
+ * @param {(string) => string} processor - function that will be called on every line
+ * @return {string} processed html
+ */
+export const processHtml = (html, processor) => {
+ const handledTags = new Set(['p', 'br', 'div'])
+ const openCloseTags = new Set(['p', 'div'])
+
+ let buffer = '' // Current output buffer
+ const level = [] // How deep we are in tags and which tags were there
+ let textBuffer = '' // Current line content
+ let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
+
+ // Extracts tag name from tag, i.e. => span
+ const getTagName = (tag) => {
+ const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag)
+ return result && (result[1] || result[2])
+ }
+
+ const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
+ if (textBuffer.trim().length > 0) {
+ buffer += processor(textBuffer)
+ } else {
+ buffer += textBuffer
+ }
+ textBuffer = ''
+ }
+
+ const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing
+ flush()
+ buffer += tag
+ }
+
+ const handleOpen = (tag) => { // handles opening tags
+ flush()
+ buffer += tag
+ level.push(tag)
+ }
+
+ const handleClose = (tag) => { // handles closing tags
+ flush()
+ buffer += tag
+ if (level[level.length - 1] === tag) {
+ level.pop()
+ }
+ }
+
+ for (let i = 0; i < html.length; i++) {
+ const char = html[i]
+ if (char === '<' && tagBuffer === null) {
+ tagBuffer = char
+ } else if (char !== '>' && tagBuffer !== null) {
+ tagBuffer += char
+ } else if (char === '>' && tagBuffer !== null) {
+ tagBuffer += char
+ const tagFull = tagBuffer
+ tagBuffer = null
+ const tagName = getTagName(tagFull)
+ if (handledTags.has(tagName)) {
+ if (tagName === 'br') {
+ handleBr(tagFull)
+ } else if (openCloseTags.has(tagName)) {
+ if (tagFull[1] === '/') {
+ handleClose(tagFull)
+ } else if (tagFull[tagFull.length - 2] === '/') {
+ // self-closing
+ handleBr(tagFull)
+ } else {
+ handleOpen(tagFull)
+ }
+ }
+ } else {
+ textBuffer += tagFull
+ }
+ } else if (char === '\n') {
+ handleBr(char)
+ } else {
+ textBuffer += char
+ }
+ }
+ if (tagBuffer) {
+ textBuffer += tagBuffer
+ }
+
+ flush()
+
+ return buffer
+}
diff --git a/static/font/LICENSE.txt b/static/font/LICENSE.txt
deleted file mode 100755
index 95966f00..00000000
--- a/static/font/LICENSE.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-Font license info
-
-
-## Font Awesome
-
- Copyright (C) 2016 by Dave Gandy
-
- Author: Dave Gandy
- License: SIL ()
- Homepage: http://fortawesome.github.com/Font-Awesome/
-
-
-## Entypo
-
- Copyright (C) 2012 by Daniel Bruce
-
- Author: Daniel Bruce
- License: SIL (http://scripts.sil.org/OFL)
- Homepage: http://www.entypo.com
-
-
-## Iconic
-
- Copyright (C) 2012 by P.J. Onori
-
- Author: P.J. Onori
- License: SIL (http://scripts.sil.org/OFL)
- Homepage: http://somerandomdude.com/work/iconic/
-
-
-## Fontelico
-
- Copyright (C) 2012 by Fontello project
-
- Author: Crowdsourced, for Fontello project
- License: SIL (http://scripts.sil.org/OFL)
- Homepage: http://fontello.com
-
-
diff --git a/static/font/README.txt b/static/font/README.txt
deleted file mode 100755
index beaab336..00000000
--- a/static/font/README.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This webfont is generated by http://fontello.com open source project.
-
-
-================================================================================
-Please, note, that you should obey original font licenses, used to make this
-webfont pack. Details available in LICENSE.txt file.
-
-- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
- site in "About" section.
-
-- If your project is open-source, usually, it will be ok to make LICENSE.txt
- file publicly available in your repository.
-
-- Fonts, used in Fontello, don't require a clickable link on your site.
- But any kind of additional authors crediting is welcome.
-================================================================================
-
-
-Comments on archive content
----------------------------
-
-- /font/* - fonts in different formats
-
-- /css/* - different kinds of css, for all situations. Should be ok with
- twitter bootstrap. Also, you can skip style and assign icon classes
- directly to text elements, if you don't mind about IE7.
-
-- demo.html - demo file, to show your webfont content
-
-- LICENSE.txt - license info about source fonts, used to build your one.
-
-- config.json - keeps your settings. You can import it back into fontello
- anytime, to continue your work
-
-
-Why so many CSS files ?
------------------------
-
-Because we like to fit all your needs :)
-
-- basic file, .css - is usually enough, it contains @font-face
- and character code definitions
-
-- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
- directly into html
-
-- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
- rules, but still wish to benefit from css generation. That can be very
- convenient for automated asset build systems. When you need to update font -
- no need to manually edit files, just override old version with archive
- content. See fontello source code for examples.
-
-- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
- CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
- We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
- server headers. But if you ok with dirty hack - this file is for you. Note,
- that data url moved to separate @font-face to avoid problems with
-
-
-
-
-
-
-
-
-
fontello font demo
-
-
-
-
-
icon-cancel0xe800
-
icon-upload0xe801
-
icon-star0xe802
-
icon-star-empty0xe803
-
-
-
icon-retweet0xe804
-
icon-eye-off0xe805
-
icon-search0xe806
-
icon-cog0xe807
-
-
-
icon-logout0xe808
-
icon-down-open0xe809
-
icon-attach0xe80a
-
icon-picture0xe80b
-
-
-
icon-video0xe80c
-
icon-right-open0xe80d
-
icon-left-open0xe80e
-
icon-up-open0xe80f
-
-
-
icon-bell-ringing-o0xe810
-
icon-lock0xe811
-
icon-globe0xe812
-
icon-brush0xe813
-
-
-
icon-attention0xe814
-
icon-plus0xe815
-
icon-adjust0xe816
-
icon-edit0xe817
-
-
-
icon-pencil0xe818
-
icon-pin0xe819
-
icon-wrench0xe81a
-
icon-chart-bar0xe81b
-
-
-
icon-zoom-in0xe81c
-
icon-spin30xe832
-
icon-spin40xe834
-
icon-link-ext0xf08e
-
-
-
icon-link-ext-alt0xf08f
-
icon-menu0xf0c9
-
icon-mail-alt0xf0e0
-
icon-gauge0xf0e4
-
-
-
icon-comment-empty0xf0e5
-
icon-bell-alt0xf0f3
-
icon-plus-squared0xf0fe
-
icon-reply0xf112
-
-
-
icon-smile0xf118
-
icon-lock-open-alt0xf13e
-
icon-ellipsis0xf141
-
icon-play-circled0xf144
-
-
-
icon-thumbs-up-alt0xf164
-
icon-binoculars0xf1e5
-
icon-user-plus0xf234
-
-
-
-
-
\ No newline at end of file
diff --git a/static/font/font/fontello.eot b/static/font/font/fontello.eot
deleted file mode 100755
index 1703fd97..00000000
Binary files a/static/font/font/fontello.eot and /dev/null differ
diff --git a/static/font/font/fontello.svg b/static/font/font/fontello.svg
deleted file mode 100755
index f5e497ce..00000000
--- a/static/font/font/fontello.svg
+++ /dev/null
@@ -1,104 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/static/font/font/fontello.ttf b/static/font/font/fontello.ttf
deleted file mode 100755
index e9ed7803..00000000
Binary files a/static/font/font/fontello.ttf and /dev/null differ
diff --git a/static/font/font/fontello.woff b/static/font/font/fontello.woff
deleted file mode 100755
index 1d5025d3..00000000
Binary files a/static/font/font/fontello.woff and /dev/null differ
diff --git a/static/font/font/fontello.woff2 b/static/font/font/fontello.woff2
deleted file mode 100755
index 078991eb..00000000
Binary files a/static/font/font/fontello.woff2 and /dev/null differ
diff --git a/static/font/config.json b/static/fontello.json
similarity index 92%
rename from static/font/config.json
rename to static/fontello.json
index c0cf1727..c1ed3393 100755
--- a/static/font/config.json
+++ b/static/fontello.json
@@ -303,6 +303,36 @@
"css": "gauge",
"code": 61668,
"src": "fontawesome"
+ },
+ {
+ "uid": "31972e4e9d080eaa796290349ae6c1fd",
+ "css": "users",
+ "code": 59421,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
+ "css": "info-circled",
+ "code": 59423,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
+ "css": "home-2",
+ "code": 59425,
+ "src": "typicons"
+ },
+ {
+ "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
+ "css": "chat",
+ "code": 59422,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3a00327e61b997b58518bd43ed83c3df",
+ "css": "login",
+ "code": 59424,
+ "src": "fontawesome"
}
]
}
\ No newline at end of file
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
index 8af05c24..45d74f14 100644
--- a/test/unit/karma.conf.js
+++ b/test/unit/karma.conf.js
@@ -5,6 +5,7 @@
// var path = require('path')
var merge = require('webpack-merge')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
var baseConfig = require('../../build/webpack.base.conf')
var utils = require('../../build/utils')
var webpack = require('webpack')
@@ -24,6 +25,11 @@ var webpackConfig = merge(baseConfig, {
plugins: [
new webpack.DefinePlugin({
'process.env': require('../../config/test.env')
+ }),
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: 'index.html',
+ inject: true
})
]
})
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index 49f378e2..cfb380ba 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -277,6 +277,19 @@ describe('API Entities normalizer', () => {
expect(parsedUser).to.have.property('description_html').that.contains(' {
+ const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: ':thinking:', value: ':image:' }] })
+
+ const parsedUser = parseUser(user)
+
+ expect(parsedUser).to.have.property('fields_html').to.be.an('array')
+
+ const field = parsedUser.fields_html[0]
+
+ expect(field).to.have.property('name').that.contains(' {
const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false, hide_followers_count: false, hide_follows_count: true } })
diff --git a/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js
new file mode 100644
index 00000000..f301429d
--- /dev/null
+++ b/test/unit/specs/services/tiny_post_html_processor/tiny_post_html_processor.spec.js
@@ -0,0 +1,96 @@
+import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
+
+describe('TinyPostHTMLProcessor', () => {
+ describe('with processor that keeps original line should not make any changes to HTML when', () => {
+ const processorKeep = (line) => line
+ it('fed with regular HTML with newlines', () => {
+ const inputOutput = '1 2
3 4
5 \n 6
7 8
\n '
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with possibly broken HTML with invalid tags/composition', () => {
+ const inputOutput = 'ayylmao '
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with very broken HTML with broken composition', () => {
+ const inputOutput = '
lmao what whats going on
wha
'
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with sorta valid HTML but tags aren\'t closed', () => {
+ const inputOutput = 'just leaving a
hanging'
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with not really HTML at this point... tags that aren\'t finished', () => {
+ const inputOutput = 'do you expect me to finish this
{
+ const inputOutput = 'look ma
p \nwithin
p!
and a
div!
'
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with maybe valid HTML? self-closing divs and ps', () => {
+ const inputOutput = 'a what now ?'
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+
+ it('fed with valid XHTML containing a CDATA', () => {
+ const inputOutput = 'Yes, it is me, '
+ expect(processHtml(inputOutput, processorKeep)).to.eql(inputOutput)
+ })
+ })
+ describe('with processor that replaces lines with word "_" should match expected line when', () => {
+ const processorReplace = (line) => '_'
+ it('fed with regular HTML with newlines', () => {
+ const input = '1 2
3 4
5 \n 6
7 8
\n '
+ const output = '_ _
_
_\n_
_ _
\n '
+ expect(processHtml(input, processorReplace)).to.eql(output)
+ })
+
+ it('fed with possibly broken HTML with invalid tags/composition', () => {
+ const input = 'ayylmao '
+ const output = '_'
+ expect(processHtml(input, processorReplace)).to.eql(output)
+ })
+
+ it('fed with very broken HTML with broken composition', () => {
+ const input = ' lmao what
whats going on
wha
'
+ const output = '
_
_
_
'
+ expect(processHtml(input, processorReplace)).to.eql(output)
+ })
+
+ it('fed with sorta valid HTML but tags aren\'t closed', () => {
+ const input = 'just leaving a
hanging'
+ const output = '_
_'
+ expect(processHtml(input, processorReplace)).to.eql(output)
+ })
+
+ it('fed with not really HTML at this point... tags that aren\'t finished', () => {
+ const input = 'do you expect me to finish this