mirror of https://github.com/status-im/codimd.git
Merge pull request #1699 from Nick0603/feature/custom-note-url
Feat/custom note url
This commit is contained in:
commit
829c961a03
|
@ -8,54 +8,13 @@ const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound, e
|
||||||
const { updateHistory, historyDelete } = require('../history')
|
const { updateHistory, historyDelete } = require('../history')
|
||||||
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
|
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
|
||||||
const realtime = require('../realtime/realtime')
|
const realtime = require('../realtime/realtime')
|
||||||
|
const service = require('./service')
|
||||||
|
|
||||||
async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
|
exports.showNote = async (req, res) => {
|
||||||
const id = await Note.parseNoteIdAsync(noteId)
|
|
||||||
|
|
||||||
const includes = []
|
|
||||||
|
|
||||||
if (includeUser) {
|
|
||||||
includes.push({
|
|
||||||
model: User,
|
|
||||||
as: 'owner'
|
|
||||||
}, {
|
|
||||||
model: User,
|
|
||||||
as: 'lastchangeuser'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = await Note.findOne({
|
|
||||||
where: {
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
include: includes
|
|
||||||
})
|
|
||||||
return note
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createNote (userId, noteAlias) {
|
|
||||||
if (!config.allowAnonymous && !userId) {
|
|
||||||
throw new Error('can not create note')
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = await Note.create({
|
|
||||||
ownerId: userId,
|
|
||||||
alias: noteAlias
|
|
||||||
})
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
updateHistory(userId, note)
|
|
||||||
}
|
|
||||||
|
|
||||||
return note
|
|
||||||
}
|
|
||||||
|
|
||||||
// controller
|
|
||||||
async function showNote (req, res) {
|
|
||||||
const noteId = req.params.noteId
|
const noteId = req.params.noteId
|
||||||
const userId = req.user ? req.user.id : null
|
const userId = req.user ? req.user.id : null
|
||||||
|
|
||||||
let note = await getNoteById(noteId)
|
let note = await service.getNote(noteId)
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
// if allow free url enable, auto create note
|
// if allow free url enable, auto create note
|
||||||
|
@ -64,7 +23,7 @@ async function showNote (req, res) {
|
||||||
} else if (!config.allowAnonymous && !userId) {
|
} else if (!config.allowAnonymous && !userId) {
|
||||||
return errorForbidden(req, res)
|
return errorForbidden(req, res)
|
||||||
}
|
}
|
||||||
note = await createNote(userId, noteId)
|
note = await service.createNote(userId, noteId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newCheckViewPermission(note, req.isAuthenticated(), userId)) {
|
if (!newCheckViewPermission(note, req.isAuthenticated(), userId)) {
|
||||||
|
@ -79,20 +38,10 @@ async function showNote (req, res) {
|
||||||
return responseCodiMD(res, note)
|
return responseCodiMD(res, note)
|
||||||
}
|
}
|
||||||
|
|
||||||
function canViewNote (note, isLogin, userId) {
|
exports.showPublishNote = async (req, res) => {
|
||||||
if (note.permission === 'private') {
|
|
||||||
return note.ownerId === userId
|
|
||||||
}
|
|
||||||
if (note.permission === 'limited' || note.permission === 'protected') {
|
|
||||||
return isLogin
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function showPublishNote (req, res) {
|
|
||||||
const shortid = req.params.shortid
|
const shortid = req.params.shortid
|
||||||
|
|
||||||
const note = await getNoteById(shortid, {
|
const note = await service.getNote(shortid, {
|
||||||
includeUser: true
|
includeUser: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -100,12 +49,12 @@ async function showPublishNote (req, res) {
|
||||||
return errorNotFound(req, res)
|
return errorNotFound(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
if (!service.canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
||||||
return errorForbidden(req, res)
|
return errorForbidden(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
|
if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
|
||||||
return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
|
return res.redirect(config.serviceerURL + '/s/' + (note.alias || note.shortid))
|
||||||
}
|
}
|
||||||
|
|
||||||
await note.increment('viewcount')
|
await note.increment('viewcount')
|
||||||
|
@ -143,16 +92,16 @@ async function showPublishNote (req, res) {
|
||||||
res.render('pretty.ejs', data)
|
res.render('pretty.ejs', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function noteActions (req, res) {
|
exports.noteActions = async (req, res) => {
|
||||||
const noteId = req.params.noteId
|
const noteId = req.params.noteId
|
||||||
|
|
||||||
const note = await getNoteById(noteId)
|
const note = await service.getNote(noteId)
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return errorNotFound(req, res)
|
return errorNotFound(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
if (!service.canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
||||||
return errorForbidden(req, res)
|
return errorForbidden(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,41 +136,13 @@ async function noteActions (req, res) {
|
||||||
actionPandoc(req, res, note)
|
actionPandoc(req, res, note)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return res.redirect(config.serverURL + '/' + noteId)
|
return res.redirect(config.serviceerURL + '/' + noteId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMyNoteList (userId, callback) {
|
exports.listMyNotes = (req, res) => {
|
||||||
const myNotes = await Note.findAll({
|
|
||||||
where: {
|
|
||||||
ownerId: userId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!myNotes) {
|
|
||||||
return callback(null, null)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const myNoteList = myNotes.map(note => ({
|
|
||||||
id: Note.encodeNoteId(note.id),
|
|
||||||
text: note.title,
|
|
||||||
tags: Note.parseNoteInfo(note.content).tags,
|
|
||||||
createdAt: note.createdAt,
|
|
||||||
lastchangeAt: note.lastchangeAt,
|
|
||||||
shortId: note.shortid
|
|
||||||
}))
|
|
||||||
if (config.debug) {
|
|
||||||
logger.info('Parse myNoteList success: ' + userId)
|
|
||||||
}
|
|
||||||
return callback(null, myNoteList)
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Parse myNoteList failed')
|
|
||||||
return callback(err, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function listMyNotes (req, res) {
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
getMyNoteList(req.user.id, (err, myNoteList) => {
|
service.getMyNoteList(req.user.id, (err, myNoteList) => {
|
||||||
if (err) return errorInternalError(req, res)
|
if (err) return errorInternalError(req, res)
|
||||||
if (!myNoteList) return errorNotFound(req, res)
|
if (!myNoteList) return errorNotFound(req, res)
|
||||||
res.send({
|
res.send({
|
||||||
|
@ -233,7 +154,7 @@ function listMyNotes (req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteNote = async (req, res) => {
|
exports.deleteNote = async (req, res) => {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
||||||
try {
|
try {
|
||||||
|
@ -267,7 +188,7 @@ const deleteNote = async (req, res) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateNote = async (req, res) => {
|
exports.updateNote = async (req, res) => {
|
||||||
if (req.isAuthenticated() || config.allowAnonymousEdits) {
|
if (req.isAuthenticated() || config.allowAnonymousEdits) {
|
||||||
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
||||||
try {
|
try {
|
||||||
|
@ -332,9 +253,43 @@ const updateNote = async (req, res) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.showNote = showNote
|
exports.updateNoteAlias = async (req, res) => {
|
||||||
exports.showPublishNote = showPublishNote
|
const originAliasOrNoteId = req.params.originAliasOrNoteId
|
||||||
exports.noteActions = noteActions
|
const alias = req.body.alias || ''
|
||||||
exports.listMyNotes = listMyNotes
|
const userId = req.user ? req.user.id : null
|
||||||
exports.deleteNote = deleteNote
|
const note = await service.getNote(originAliasOrNoteId)
|
||||||
exports.updateNote = updateNote
|
.catch((err) => {
|
||||||
|
logger.error('get note failed:' + err.message)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
logger.error('update note alias failed: note not found.')
|
||||||
|
return res.status(500).json({ status: 'error', message: 'Internal Error' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.ownerId !== userId) {
|
||||||
|
return res.status(403).json({ status: 'error', message: 'Forbidden' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await service.updateAlias(originAliasOrNoteId, alias)
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error('update note alias failed:' + err.message)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return res.status(500).json({ status: 'error', message: 'Internal Error' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isSuccess, errorMessage } = result
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
res.status(400).json({ status: 'error', message: errorMessage })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
status: 'ok'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
const { Note, User } = require('../models')
|
||||||
|
const config = require('../config')
|
||||||
|
const logger = require('../logger')
|
||||||
|
const realtime = require('../realtime/realtime')
|
||||||
|
const { updateHistory } = require('../history')
|
||||||
|
|
||||||
|
const EXIST_WEB_ROUTES = ['', 'new', 'me', 'history', '403', '404', '500', 'config']
|
||||||
|
const FORBIDDEN_ALIASES = [...EXIST_WEB_ROUTES, ...config.forbiddenNoteIDs]
|
||||||
|
|
||||||
|
exports.getNote = async (originAliasOrNoteId, { includeUser } = { includeUser: false }) => {
|
||||||
|
const id = await Note.parseNoteIdAsync(originAliasOrNoteId)
|
||||||
|
|
||||||
|
const includes = []
|
||||||
|
|
||||||
|
if (includeUser) {
|
||||||
|
includes.push({
|
||||||
|
model: User,
|
||||||
|
as: 'owner'
|
||||||
|
}, {
|
||||||
|
model: User,
|
||||||
|
as: 'lastchangeuser'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = await Note.findOne({
|
||||||
|
where: {
|
||||||
|
id: id
|
||||||
|
},
|
||||||
|
include: includes
|
||||||
|
})
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createNote = async (userId, noteAlias) => {
|
||||||
|
if (!config.allowAnonymous && !userId) {
|
||||||
|
throw new Error('can not create note')
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = await Note.create({
|
||||||
|
ownerId: userId,
|
||||||
|
alias: noteAlias
|
||||||
|
})
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
updateHistory(userId, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.canViewNote = (note, isLogin, userId) => {
|
||||||
|
if (note.permission === 'private') {
|
||||||
|
return note.ownerId === userId
|
||||||
|
}
|
||||||
|
if (note.permission === 'limited' || note.permission === 'protected') {
|
||||||
|
return isLogin
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getMyNoteList = async (userId, callback) => {
|
||||||
|
const myNotes = await Note.findAll({
|
||||||
|
where: {
|
||||||
|
ownerId: userId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!myNotes) {
|
||||||
|
return callback(null, null)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const myNoteList = myNotes.map(note => ({
|
||||||
|
id: Note.encodeNoteId(note.id),
|
||||||
|
text: note.title,
|
||||||
|
tags: Note.parseNoteInfo(note.content).tags,
|
||||||
|
createdAt: note.createdAt,
|
||||||
|
lastchangeAt: note.lastchangeAt,
|
||||||
|
shortId: note.shortid
|
||||||
|
}))
|
||||||
|
if (config.debug) {
|
||||||
|
logger.info('Parse myNoteList success: ' + userId)
|
||||||
|
}
|
||||||
|
return callback(null, myNoteList)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Parse myNoteList failed')
|
||||||
|
return callback(err, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeAlias = (alias) => {
|
||||||
|
return alias.replace(/( |\/)/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkAliasValid = async (originAliasOrNoteId, alias) => {
|
||||||
|
const sanitizedAlias = sanitizeAlias(alias)
|
||||||
|
if (FORBIDDEN_ALIASES.includes(sanitizedAlias)) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: 'The url is exist.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[0-9a-z-_]+$/.test(alias)) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: 'The url must be lowercase letters, decimal digits, hyphen or underscore.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflictNote = await exports.getNote(alias)
|
||||||
|
const note = await exports.getNote(originAliasOrNoteId)
|
||||||
|
|
||||||
|
if (conflictNote && conflictNote.id !== note.id) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: 'The url is exist.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateAlias = async (originAliasOrNoteId, alias) => {
|
||||||
|
const sanitizedAlias = sanitizeAlias(alias)
|
||||||
|
const note = await exports.getNote(originAliasOrNoteId)
|
||||||
|
const { isValid, errorMessage } = await checkAliasValid(originAliasOrNoteId, alias)
|
||||||
|
if (!isValid) {
|
||||||
|
return {
|
||||||
|
isSuccess: false,
|
||||||
|
errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedNote = await note.update({
|
||||||
|
alias: sanitizedAlias,
|
||||||
|
lastchangeAt: Date.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
realtime.io.to(updatedNote.id)
|
||||||
|
.emit('alias updated', {
|
||||||
|
alias: updatedNote.alias
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSuccess: true
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,7 +71,9 @@ appRouter.get('/s/:shortid/:action', response.publishNoteActions)
|
||||||
appRouter.get('/p/:shortid', response.showPublishSlide)
|
appRouter.get('/p/:shortid', response.showPublishSlide)
|
||||||
// publish slide actions
|
// publish slide actions
|
||||||
appRouter.get('/p/:shortid/:action', response.publishSlideActions)
|
appRouter.get('/p/:shortid/:action', response.publishSlideActions)
|
||||||
// gey my note list
|
// update note alias
|
||||||
|
appRouter.patch('/api/notes/:originAliasOrNoteId/alias', bodyParser.json(), noteController.updateNoteAlias)
|
||||||
|
// get my note list
|
||||||
appRouter.get('/api/notes/myNotes', noteController.listMyNotes)
|
appRouter.get('/api/notes/myNotes', noteController.listMyNotes)
|
||||||
// delete note by id
|
// delete note by id
|
||||||
appRouter.delete('/api/notes/:noteId', noteController.deleteNote)
|
appRouter.delete('/api/notes/:noteId', noteController.deleteNote)
|
||||||
|
|
|
@ -117,5 +117,7 @@
|
||||||
"Powered by %s": "Powered by %s",
|
"Powered by %s": "Powered by %s",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
"Export with pandoc": "Export with pandoc",
|
"Export with pandoc": "Export with pandoc",
|
||||||
"Select output format": "Select output format"
|
"Select output format": "Select output format",
|
||||||
|
"Custom Note Url": "Custom Note Url",
|
||||||
|
"Submit": "Submit"
|
||||||
}
|
}
|
|
@ -117,5 +117,7 @@
|
||||||
"Powered by %s": "技術支援:%s",
|
"Powered by %s": "技術支援:%s",
|
||||||
"Register": "註冊",
|
"Register": "註冊",
|
||||||
"Export with pandoc": "使用 pandoc 匯出",
|
"Export with pandoc": "使用 pandoc 匯出",
|
||||||
"Select output format": "選擇輸出格式"
|
"Select output format": "選擇輸出格式",
|
||||||
|
"Custom Note Url": "自訂筆記網址",
|
||||||
|
"Submit": "送出"
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,14 +26,7 @@ import {
|
||||||
setloginStateChangeEvent
|
setloginStateChangeEvent
|
||||||
} from './lib/common/login'
|
} from './lib/common/login'
|
||||||
|
|
||||||
import {
|
import { getConfig } from './lib/config'
|
||||||
debug,
|
|
||||||
DROPBOX_APP_KEY,
|
|
||||||
noteid,
|
|
||||||
noteurl,
|
|
||||||
urlpath,
|
|
||||||
version
|
|
||||||
} from './lib/config'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
autoLinkify,
|
autoLinkify,
|
||||||
|
@ -91,6 +84,24 @@ require('spin.js/spin.css')
|
||||||
|
|
||||||
require('highlight.js/styles/github-gist.css')
|
require('highlight.js/styles/github-gist.css')
|
||||||
|
|
||||||
|
let debug,
|
||||||
|
DROPBOX_APP_KEY,
|
||||||
|
noteid,
|
||||||
|
noteurl,
|
||||||
|
urlpath,
|
||||||
|
version
|
||||||
|
|
||||||
|
function updateConfig () {
|
||||||
|
const config = getConfig()
|
||||||
|
debug = config.debug
|
||||||
|
DROPBOX_APP_KEY = config.DROPBOX_APP_KEY
|
||||||
|
noteid = config.noteid
|
||||||
|
noteurl = config.noteurl
|
||||||
|
urlpath = config.urlpath
|
||||||
|
version = config.version
|
||||||
|
}
|
||||||
|
updateConfig()
|
||||||
|
|
||||||
var defaultTextHeight = 20
|
var defaultTextHeight = 20
|
||||||
var viewportMargin = 20
|
var viewportMargin = 20
|
||||||
var defaultEditorMode = 'gfm'
|
var defaultEditorMode = 'gfm'
|
||||||
|
@ -1266,6 +1277,62 @@ $('#revisionModalRevert').click(function () {
|
||||||
editor.setValue(revision.content)
|
editor.setValue(revision.content)
|
||||||
ui.modal.revision.modal('hide')
|
ui.modal.revision.modal('hide')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// custom note url modal
|
||||||
|
const updateNoteUrl = (noteUrl = '') => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
$.ajax({
|
||||||
|
method: 'PATCH',
|
||||||
|
url: `/api/notes/${noteid}/alias`,
|
||||||
|
data: JSON.stringify({
|
||||||
|
alias: noteUrl
|
||||||
|
}),
|
||||||
|
contentType: 'application/json;charset=utf-8',
|
||||||
|
success: resolve,
|
||||||
|
error: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.modal.customNoteUrl.on('submit', function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const showErrorMessage = (msg) => {
|
||||||
|
ui.modal.customNoteUrl.find('.js-error-message').text(msg)
|
||||||
|
ui.modal.customNoteUrl.find('.js-error-alert').show()
|
||||||
|
}
|
||||||
|
const hideErrorMessage = () => ui.modal.customNoteUrl.find('.js-error-alert').hide()
|
||||||
|
|
||||||
|
const customUrl = ui.modal.customNoteUrl.find('[name="custom-url"]').val()
|
||||||
|
if (!/^[0-9a-z-_]+$/.test(customUrl)) {
|
||||||
|
showErrorMessage('The url must be lowercase letters, decimal digits, hyphen or underscore.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNoteUrl(customUrl)
|
||||||
|
.then(
|
||||||
|
({ status }) => {
|
||||||
|
if (status === 'ok') {
|
||||||
|
hideErrorMessage()
|
||||||
|
ui.modal.customNoteUrl.modal('hide')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err.status === 400 && err.responseJSON.message) {
|
||||||
|
showErrorMessage(err.responseJSON.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (err.status === 403) {
|
||||||
|
showErrorMessage('Only note owner can edit custom url.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showErrorMessage('Something wrong.')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
showErrorMessage('Something wrong.')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// snippet projects
|
// snippet projects
|
||||||
ui.modal.snippetImportProjects.change(function () {
|
ui.modal.snippetImportProjects.change(function () {
|
||||||
var accesstoken = $('#snippetImportModalAccessToken').val()
|
var accesstoken = $('#snippetImportModalAccessToken').val()
|
||||||
|
@ -1803,6 +1870,7 @@ socket.on('version', function (data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var authors = []
|
var authors = []
|
||||||
var authorship = []
|
var authorship = []
|
||||||
var authorMarks = {} // temp variable
|
var authorMarks = {} // temp variable
|
||||||
|
@ -2217,6 +2285,12 @@ socket.on('cursor blur', function (data) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('alias updated', function (data) {
|
||||||
|
const alias = data.alias
|
||||||
|
history.replaceState({}, '', alias)
|
||||||
|
updateConfig()
|
||||||
|
})
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
valueNames: ['id', 'name'],
|
valueNames: ['id', 'name'],
|
||||||
item: '<li class="ui-user-item">' +
|
item: '<li class="ui-user-item">' +
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || ''
|
export function getConfig () {
|
||||||
|
return {
|
||||||
|
DROPBOX_APP_KEY: window.DROPBOX_APP_KEY || '',
|
||||||
|
domain: window.domain || '',
|
||||||
|
urlpath: window.urlpath || '',
|
||||||
|
debug: window.debug || false,
|
||||||
|
serverurl: `${window.location.protocol}//${domain || window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`,
|
||||||
|
port: window.location.port,
|
||||||
|
noteid: decodeURIComponent(urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1]),
|
||||||
|
noteurl: `${serverurl}/${noteid}`,
|
||||||
|
version: window.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const domain = window.domain || '' // domain name
|
const config = getConfig()
|
||||||
export const urlpath = window.urlpath || '' // sub url path, like: www.example.com/<urlpath>
|
window.serverurl = config.serverurl
|
||||||
export const debug = window.debug || false
|
|
||||||
|
|
||||||
export const port = window.location.port
|
export const DROPBOX_APP_KEY = config.DROPBOX_APP_KEY
|
||||||
export const serverurl = `${window.location.protocol}//${domain || window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`
|
export const domain = config.domain
|
||||||
window.serverurl = serverurl
|
export const urlpath = config.urlpath
|
||||||
export const noteid = decodeURIComponent(urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1])
|
export const debug = config.debug
|
||||||
export const noteurl = `${serverurl}/${noteid}`
|
export const port = config.port
|
||||||
|
export const serverurl = config.serverurl
|
||||||
export const version = window.version
|
export const noteid = config.noteid
|
||||||
|
export const noteurl = config.noteurl
|
||||||
|
export const version = config.version
|
||||||
|
|
|
@ -83,7 +83,8 @@ export const getUIElements = () => ({
|
||||||
snippetImportProjects: $('#snippetImportModalProjects'),
|
snippetImportProjects: $('#snippetImportModalProjects'),
|
||||||
snippetImportSnippets: $('#snippetImportModalSnippets'),
|
snippetImportSnippets: $('#snippetImportModalSnippets'),
|
||||||
revision: $('#revisionModal'),
|
revision: $('#revisionModal'),
|
||||||
pandocExport: $('.pandoc-export-modal')
|
pandocExport: $('.pandoc-export-modal'),
|
||||||
|
customNoteUrl: $('#customNoteUrlModal')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -251,3 +251,4 @@
|
||||||
<%- include ../shared/help-modal %>
|
<%- include ../shared/help-modal %>
|
||||||
<%- include ../shared/revision-modal %>
|
<%- include ../shared/revision-modal %>
|
||||||
<%- include ../shared/pandoc-export-modal %>
|
<%- include ../shared/pandoc-export-modal %>
|
||||||
|
<%- include ../shared/custom-note-url-modal %>
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header"><%= __('Extra') %></li>
|
<li class="dropdown-header"><%= __('Extra') %></li>
|
||||||
|
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#customNoteUrlModal"><i class="fa fa-pencil-square-o fa-fw"></i> <%= __('Custom Note Url') %></a>
|
||||||
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> <%= __('Revision') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> <%= __('Revision') %></a>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||||
|
@ -132,6 +134,8 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu list" role="menu" aria-labelledby="menu">
|
<ul class="dropdown-menu list" role="menu" aria-labelledby="menu">
|
||||||
<li class="dropdown-header"><%= __('Extra') %></li>
|
<li class="dropdown-header"><%= __('Extra') %></li>
|
||||||
|
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#customNoteUrlModal"><i class="fa fa-pencil-square-o fa-fw"></i> <%= __('Custom Note Url') %></a>
|
||||||
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> <%= __('Revision') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> <%= __('Revision') %></a>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!-- custom-note-url-modal -->
|
||||||
|
<div class="modal fade" id="customNoteUrlModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form role="form">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||||
|
aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title"><i class="fa fa-pencil-square-o"></i>
|
||||||
|
<%= __('Custom Note Url') %>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||||
|
<span>Notice: the previous custom note url will not be redirected to the new one, please take your own risk to change this.</span>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger js-error-alert" style="display: none" role="alert">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||||
|
<span class="js-error-message"></span>
|
||||||
|
</div>
|
||||||
|
<p>For example: /hello-world</p>
|
||||||
|
<input type="text" class="form-control" name="custom-url" placeholder="hello-world" required>
|
||||||
|
<div class="help-block with-errors"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group text-right">
|
||||||
|
<button type="submit" class="btn btn-primary" style="margin-left: auto;">
|
||||||
|
<%= __('Submit') %>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue