Merge branch 'greentext-strikes-back' into shigusegubu
* greentext-strikes-back: eslint making dtluna proud or disgusted Made it optional Fix regex, tag detector condition adds greentext, also small fixes achieve the perfect speeeeen in media-upload
This commit is contained in:
commit
31aabd5fc7
10 changed files with 166 additions and 26 deletions
|
@ -6,16 +6,16 @@
|
||||||
@drop="fileDrop"
|
@drop="fileDrop"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-default"
|
class="label"
|
||||||
:title="$t('tool_tip.media_upload')"
|
:title="$t('tool_tip.media_upload')"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="uploading"
|
v-if="uploading"
|
||||||
class="icon-spin4 animate-spin"
|
class="progress-icon icon-spin4 animate-spin"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
v-if="!uploading"
|
v-if="!uploading"
|
||||||
class="icon-upload"
|
class="new-icon icon-upload"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="uploadReady"
|
v-if="uploadReady"
|
||||||
|
@ -30,15 +30,24 @@
|
||||||
|
|
||||||
<script src="./media_upload.js" ></script>
|
<script src="./media_upload.js" ></script>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.media-upload {
|
.media-upload {
|
||||||
.icon-upload {
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
.progress-icon {
|
||||||
display: block;
|
display: inline-block;
|
||||||
width: 100%;
|
line-height: 0;
|
||||||
|
&::before {
|
||||||
|
/* Overriding fontello to achieve the perfect speeeen */
|
||||||
|
margin: 0;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -339,11 +339,6 @@
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
i {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected, &:hover {
|
&.selected, &:hover {
|
||||||
// needs to be specific to override icon default color
|
// needs to be specific to override icon default color
|
||||||
i, label {
|
i, label {
|
||||||
|
|
|
@ -270,6 +270,17 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.fun') }}</h2>
|
||||||
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="greentext">
|
||||||
|
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.theme')">
|
<div :label="$t('settings.theme')">
|
||||||
|
|
|
@ -13,10 +13,11 @@ import Timeago from '../timeago/timeago.vue'
|
||||||
import StatusPopover from '../status_popover/status_popover.vue'
|
import StatusPopover from '../status_popover/status_popover.vue'
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
import fileType from 'src/services/file_type/file_type.service'
|
import fileType from 'src/services/file_type/file_type.service'
|
||||||
|
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
|
||||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||||
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
|
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
|
||||||
import { filter, unescape, uniqBy } from 'lodash'
|
import { filter, unescape, uniqBy } from 'lodash'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
|
|
||||||
const Status = {
|
const Status = {
|
||||||
name: 'Status',
|
name: 'Status',
|
||||||
|
@ -42,8 +43,8 @@ const Status = {
|
||||||
showingTall: this.inConversation && this.focused,
|
showingTall: this.inConversation && this.focused,
|
||||||
showingLongSubject: false,
|
showingLongSubject: false,
|
||||||
error: null,
|
error: null,
|
||||||
expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
|
// not as computed because it sets the initial state which will be changed later
|
||||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter
|
expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -103,7 +104,7 @@ const Status = {
|
||||||
return this.$store.state.statuses.allStatusesObject[this.status.id]
|
return this.$store.state.statuses.allStatusesObject[this.status.id]
|
||||||
},
|
},
|
||||||
loggedIn () {
|
loggedIn () {
|
||||||
return !!this.$store.state.users.currentUser
|
return !!this.currentUser
|
||||||
},
|
},
|
||||||
muteWordHits () {
|
muteWordHits () {
|
||||||
const statusText = this.status.text.toLowerCase()
|
const statusText = this.status.text.toLowerCase()
|
||||||
|
@ -163,7 +164,7 @@ const Status = {
|
||||||
if (this.inConversation || !this.isReply) {
|
if (this.inConversation || !this.isReply) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.status.user.id === this.$store.state.users.currentUser.id) {
|
if (this.status.user.id === this.currentUser.id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.status.type === 'retweet') {
|
if (this.status.type === 'retweet') {
|
||||||
|
@ -178,7 +179,7 @@ const Status = {
|
||||||
if (checkFollowing && taggedUser && taggedUser.following) {
|
if (checkFollowing && taggedUser && taggedUser.following) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
|
if (this.status.attentions[i].id === this.currentUser.id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,11 +256,41 @@ const Status = {
|
||||||
maxThumbnails () {
|
maxThumbnails () {
|
||||||
return this.mergedConfig.maxThumbnails
|
return this.mergedConfig.maxThumbnails
|
||||||
},
|
},
|
||||||
|
postBodyHtml () {
|
||||||
|
const html = this.status.statusnet_html
|
||||||
|
|
||||||
|
if (this.mergedConfig.greentext) {
|
||||||
|
try {
|
||||||
|
if (html.includes('>')) {
|
||||||
|
// This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
|
||||||
|
return processHtml(html, (string) => {
|
||||||
|
if (string.includes('>') &&
|
||||||
|
string
|
||||||
|
.replace(/<[^>]+?>/gi, '') // remove all tags
|
||||||
|
.replace(/@\w+/gi, '') // remove mentions (even failed ones)
|
||||||
|
.trim()
|
||||||
|
.startsWith('>')) {
|
||||||
|
return `<span class='greentext'>${string}</span>`
|
||||||
|
} else {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.err('Failed to process status html', e)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
},
|
||||||
contentHtml () {
|
contentHtml () {
|
||||||
if (!this.status.summary_html) {
|
if (!this.status.summary_html) {
|
||||||
return this.status.statusnet_html
|
return this.postBodyHtml
|
||||||
}
|
}
|
||||||
return this.status.summary_html + '<br />' + this.status.statusnet_html
|
return this.status.summary_html + '<br />' + this.postBodyHtml
|
||||||
},
|
},
|
||||||
combinedFavsAndRepeatsUsers () {
|
combinedFavsAndRepeatsUsers () {
|
||||||
// Use the status from the global status repository since favs and repeats are saved in it
|
// Use the status from the global status repository since favs and repeats are saved in it
|
||||||
|
@ -270,7 +301,7 @@ const Status = {
|
||||||
return uniqBy(combinedUsers, 'id')
|
return uniqBy(combinedUsers, 'id')
|
||||||
},
|
},
|
||||||
ownStatus () {
|
ownStatus () {
|
||||||
return this.status.user.id === this.$store.state.users.currentUser.id
|
return this.status.user.id === this.currentUser.id
|
||||||
},
|
},
|
||||||
tags () {
|
tags () {
|
||||||
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
|
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
|
||||||
|
@ -278,7 +309,11 @@ const Status = {
|
||||||
hidePostStats () {
|
hidePostStats () {
|
||||||
return this.mergedConfig.hidePostStats
|
return this.mergedConfig.hidePostStats
|
||||||
},
|
},
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig']),
|
||||||
|
...mapState({
|
||||||
|
betterShadow: state => state.interface.browserSupport.cssFilter,
|
||||||
|
currentUser: state => state.users.currentUser
|
||||||
|
})
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Attachment,
|
Attachment,
|
||||||
|
|
|
@ -606,7 +606,7 @@ $status-margin: 0.75em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
|
mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
|
||||||
linear-gradient(to top, white, white);
|
linear-gradient(to top, white, white);
|
||||||
// Autoprefixed seem to ignore this one, and also syntax is different
|
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
}
|
}
|
||||||
|
@ -752,7 +752,8 @@ $status-margin: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greentext {
|
.greentext {
|
||||||
color: green;
|
color: $fallback--cGreen;
|
||||||
|
color: var(--cGreen, $fallback--cGreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-conversation {
|
.status-conversation {
|
||||||
|
|
|
@ -370,6 +370,8 @@
|
||||||
"false": "no",
|
"false": "no",
|
||||||
"true": "yes"
|
"true": "yes"
|
||||||
},
|
},
|
||||||
|
"fun": "Fun",
|
||||||
|
"greentext": "Meme arrows",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"notification_setting": "Receive notifications from:",
|
"notification_setting": "Receive notifications from:",
|
||||||
"notification_setting_follows": "Users you follow",
|
"notification_setting_follows": "Users you follow",
|
||||||
|
|
|
@ -174,6 +174,8 @@
|
||||||
"name_bio": "Имя и описание",
|
"name_bio": "Имя и описание",
|
||||||
"new_email": "Новый email",
|
"new_email": "Новый email",
|
||||||
"new_password": "Новый пароль",
|
"new_password": "Новый пароль",
|
||||||
|
"fun": "Потешное",
|
||||||
|
"greentext": "Мемные стрелочки",
|
||||||
"notification_visibility": "Показывать уведомления",
|
"notification_visibility": "Показывать уведомления",
|
||||||
"notification_visibility_follows": "Подписки",
|
"notification_visibility_follows": "Подписки",
|
||||||
"notification_visibility_likes": "Лайки",
|
"notification_visibility_likes": "Лайки",
|
||||||
|
|
|
@ -45,6 +45,7 @@ export const defaultState = {
|
||||||
playVideosInModal: false,
|
playVideosInModal: false,
|
||||||
useOneClickNsfw: false,
|
useOneClickNsfw: false,
|
||||||
useContainFit: false,
|
useContainFit: false,
|
||||||
|
greentext: undefined, // instance default
|
||||||
hidePostStats: undefined, // instance default
|
hidePostStats: undefined, // instance default
|
||||||
hideUserStats: undefined // instance default
|
hideUserStats: undefined // instance default
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ const defaultState = {
|
||||||
noAttachmentLinks: false,
|
noAttachmentLinks: false,
|
||||||
showFeaturesPanel: true,
|
showFeaturesPanel: true,
|
||||||
minimalScopesMode: false,
|
minimalScopesMode: false,
|
||||||
|
greentext: false,
|
||||||
|
|
||||||
// Nasty stuff
|
// Nasty stuff
|
||||||
pleromaBackend: true,
|
pleromaBackend: true,
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @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 a="b"> => 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
|
||||||
|
buffer += processor(textBuffer)
|
||||||
|
textBuffer = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBr = (tag) => { // handles single newlines/linebreaks
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if (openCloseTags.has(tagFull)) {
|
||||||
|
if (tagFull[1] === '/') {
|
||||||
|
handleClose(tagFull)
|
||||||
|
} else {
|
||||||
|
handleOpen(tagFull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textBuffer += tagFull
|
||||||
|
}
|
||||||
|
} else if (char === '\n') {
|
||||||
|
handleBr(char)
|
||||||
|
} else {
|
||||||
|
textBuffer += char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flush()
|
||||||
|
|
||||||
|
return buffer
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue